Proper board and holdings size when switching to variants gothic, capablanca, great...
[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
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958
1959    
1960    if (appData.debugMode) {
1961      fprintf(debugFP, "Switch board from %s to %s\n",
1962              VariantName(gameInfo.variant), VariantName(newVariant));
1963      setbuf(debugFP, NULL);
1964    }
1965    shuffleOpenings = 0;       /* [HGM] shuffle */
1966    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1967    switch(newVariant) 
1968      {
1969      case VariantShogi:
1970        newWidth = 9;  newHeight = 9;
1971        gameInfo.holdingsSize = 7;
1972      case VariantBughouse:
1973      case VariantCrazyhouse:
1974        newHoldingsWidth = 2; break;
1975      case VariantGreat:
1976        newWidth = 10;
1977      case VariantSuper:
1978        newHoldingsWidth = 2;
1979        gameInfo.holdingsSize = 8;
1980        return;
1981      case VariantGothic:
1982      case VariantCapablanca:
1983      case VariantCapaRandom:
1984        newWidth = 10;
1985      default:
1986        newHoldingsWidth = gameInfo.holdingsSize = 0;
1987      };
1988    
1989    if(newWidth  != gameInfo.boardWidth  ||
1990       newHeight != gameInfo.boardHeight ||
1991       newHoldingsWidth != gameInfo.holdingsWidth ) {
1992      
1993      /* shift position to new playing area, if needed */
1994      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995        for(i=0; i<BOARD_HEIGHT; i++) 
1996          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998              board[i][j];
1999        for(i=0; i<newHeight; i++) {
2000          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002        }
2003      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004        for(i=0; i<BOARD_HEIGHT; i++)
2005          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007              board[i][j];
2008      }
2009      gameInfo.boardWidth  = newWidth;
2010      gameInfo.boardHeight = newHeight;
2011      gameInfo.holdingsWidth = newHoldingsWidth;
2012      gameInfo.variant = newVariant;
2013      InitDrawingSizes(-2, 0);
2014      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016    
2017    DrawPosition(TRUE, boards[currentMove]);
2018 }
2019
2020 static int loggedOn = FALSE;
2021
2022 /*-- Game start info cache: --*/
2023 int gs_gamenum;
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     char str[500];
2060     int i, oldi;
2061     int buf_len;
2062     int next_out;
2063     int tkind;
2064     int backup;    /* [DM] For zippy color lines */
2065     char *p;
2066     char talker[MSG_SIZ]; // [HGM] chat
2067     int channel;
2068
2069     if (appData.debugMode) {
2070       if (!error) {
2071         fprintf(debugFP, "<ICS: ");
2072         show_bytes(debugFP, data, count);
2073         fprintf(debugFP, "\n");
2074       }
2075     }
2076
2077     if (appData.debugMode) { int f = forwardMostMove;
2078         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2079                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2080     }
2081     if (count > 0) {
2082         /* If last read ended with a partial line that we couldn't parse,
2083            prepend it to the new read and try again. */
2084         if (leftover_len > 0) {
2085             for (i=0; i<leftover_len; i++)
2086               buf[i] = buf[leftover_start + i];
2087         }
2088
2089         /* Copy in new characters, removing nulls and \r's */
2090         buf_len = leftover_len;
2091         for (i = 0; i < count; i++) {
2092             if (data[i] != NULLCHAR && data[i] != '\r')
2093               buf[buf_len++] = data[i];
2094             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2095                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2096                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2097                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2098                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2099             }
2100         }
2101
2102         buf[buf_len] = NULLCHAR;
2103         next_out = leftover_len;
2104         leftover_start = 0;
2105         
2106         i = 0;
2107         while (i < buf_len) {
2108             /* Deal with part of the TELNET option negotiation
2109                protocol.  We refuse to do anything beyond the
2110                defaults, except that we allow the WILL ECHO option,
2111                which ICS uses to turn off password echoing when we are
2112                directly connected to it.  We reject this option
2113                if localLineEditing mode is on (always on in xboard)
2114                and we are talking to port 23, which might be a real
2115                telnet server that will try to keep WILL ECHO on permanently.
2116              */
2117             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2118                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2119                 unsigned char option;
2120                 oldi = i;
2121                 switch ((unsigned char) buf[++i]) {
2122                   case TN_WILL:
2123                     if (appData.debugMode)
2124                       fprintf(debugFP, "\n<WILL ");
2125                     switch (option = (unsigned char) buf[++i]) {
2126                       case TN_ECHO:
2127                         if (appData.debugMode)
2128                           fprintf(debugFP, "ECHO ");
2129                         /* Reply only if this is a change, according
2130                            to the protocol rules. */
2131                         if (remoteEchoOption) break;
2132                         if (appData.localLineEditing &&
2133                             atoi(appData.icsPort) == TN_PORT) {
2134                             TelnetRequest(TN_DONT, TN_ECHO);
2135                         } else {
2136                             EchoOff();
2137                             TelnetRequest(TN_DO, TN_ECHO);
2138                             remoteEchoOption = TRUE;
2139                         }
2140                         break;
2141                       default:
2142                         if (appData.debugMode)
2143                           fprintf(debugFP, "%d ", option);
2144                         /* Whatever this is, we don't want it. */
2145                         TelnetRequest(TN_DONT, option);
2146                         break;
2147                     }
2148                     break;
2149                   case TN_WONT:
2150                     if (appData.debugMode)
2151                       fprintf(debugFP, "\n<WONT ");
2152                     switch (option = (unsigned char) buf[++i]) {
2153                       case TN_ECHO:
2154                         if (appData.debugMode)
2155                           fprintf(debugFP, "ECHO ");
2156                         /* Reply only if this is a change, according
2157                            to the protocol rules. */
2158                         if (!remoteEchoOption) break;
2159                         EchoOn();
2160                         TelnetRequest(TN_DONT, TN_ECHO);
2161                         remoteEchoOption = FALSE;
2162                         break;
2163                       default:
2164                         if (appData.debugMode)
2165                           fprintf(debugFP, "%d ", (unsigned char) option);
2166                         /* Whatever this is, it must already be turned
2167                            off, because we never agree to turn on
2168                            anything non-default, so according to the
2169                            protocol rules, we don't reply. */
2170                         break;
2171                     }
2172                     break;
2173                   case TN_DO:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<DO ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       default:
2178                         /* Whatever this is, we refuse to do it. */
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", option);
2181                         TelnetRequest(TN_WONT, option);
2182                         break;
2183                     }
2184                     break;
2185                   case TN_DONT:
2186                     if (appData.debugMode)
2187                       fprintf(debugFP, "\n<DONT ");
2188                     switch (option = (unsigned char) buf[++i]) {
2189                       default:
2190                         if (appData.debugMode)
2191                           fprintf(debugFP, "%d ", option);
2192                         /* Whatever this is, we are already not doing
2193                            it, because we never agree to do anything
2194                            non-default, so according to the protocol
2195                            rules, we don't reply. */
2196                         break;
2197                     }
2198                     break;
2199                   case TN_IAC:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<IAC ");
2202                     /* Doubled IAC; pass it through */
2203                     i--;
2204                     break;
2205                   default:
2206                     if (appData.debugMode)
2207                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2208                     /* Drop all other telnet commands on the floor */
2209                     break;
2210                 }
2211                 if (oldi > next_out)
2212                   SendToPlayer(&buf[next_out], oldi - next_out);
2213                 if (++i > next_out)
2214                   next_out = i;
2215                 continue;
2216             }
2217                 
2218             /* OK, this at least will *usually* work */
2219             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2220                 loggedOn = TRUE;
2221             }
2222             
2223             if (loggedOn && !intfSet) {
2224                 if (ics_type == ICS_ICC) {
2225                   sprintf(str,
2226                           "/set-quietly interface %s\n/set-quietly style 12\n",
2227                           programVersion);
2228           if (!appData.noJoin)
2229               strcat(str, "/set-quietly wrap 0\n");
2230                 } else if (ics_type == ICS_CHESSNET) {
2231                   sprintf(str, "/style 12\n");
2232                 } else {
2233                   strcpy(str, "alias $ @\n$set interface ");
2234                   strcat(str, programVersion);
2235                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2236 #ifdef WIN32
2237                   strcat(str, "$iset nohighlight 1\n");
2238 #endif
2239           if (!appData.noJoin)
2240               strcat(str, "$iset nowrap 1\n");
2241                   strcat(str, "$iset lock 1\n$style 12\n");
2242                 }
2243                 SendToICS(str);
2244                 NotifyFrontendLogin();
2245                 intfSet = TRUE;
2246             }
2247
2248             if (started == STARTED_COMMENT) {
2249                 /* Accumulate characters in comment */
2250                 parse[parse_pos++] = buf[i];
2251                 if (buf[i] == '\n') {
2252                     parse[parse_pos] = NULLCHAR;
2253                     if(chattingPartner>=0) {
2254                         char mess[MSG_SIZ];
2255                         sprintf(mess, "%s%s", talker, parse);
2256                         OutputChatMessage(chattingPartner, mess);
2257                         chattingPartner = -1;
2258                     } else
2259                     if(!suppressKibitz) // [HGM] kibitz
2260                         AppendComment(forwardMostMove, StripHighlight(parse));
2261                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2262                         int nrDigit = 0, nrAlph = 0, i;
2263                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2264                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2265                         parse[parse_pos] = NULLCHAR;
2266                         // try to be smart: if it does not look like search info, it should go to
2267                         // ICS interaction window after all, not to engine-output window.
2268                         for(i=0; i<parse_pos; i++) { // count letters and digits
2269                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2270                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2271                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2272                         }
2273                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2274                             int depth=0; float score;
2275                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2276                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2277                                 pvInfoList[forwardMostMove-1].depth = depth;
2278                                 pvInfoList[forwardMostMove-1].score = 100*score;
2279                             }
2280                             OutputKibitz(suppressKibitz, parse);
2281                         } else {
2282                             char tmp[MSG_SIZ];
2283                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2284                             SendToPlayer(tmp, strlen(tmp));
2285                         }
2286                     }
2287                     started = STARTED_NONE;
2288                 } else {
2289                     /* Don't match patterns against characters in chatter */
2290                     i++;
2291                     continue;
2292                 }
2293             }
2294             if (started == STARTED_CHATTER) {
2295                 if (buf[i] != '\n') {
2296                     /* Don't match patterns against characters in chatter */
2297                     i++;
2298                     continue;
2299                 }
2300                 started = STARTED_NONE;
2301             }
2302
2303             /* Kludge to deal with rcmd protocol */
2304             if (firstTime && looking_at(buf, &i, "\001*")) {
2305                 DisplayFatalError(&buf[1], 0, 1);
2306                 continue;
2307             } else {
2308                 firstTime = FALSE;
2309             }
2310
2311             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2312                 ics_type = ICS_ICC;
2313                 ics_prefix = "/";
2314                 if (appData.debugMode)
2315                   fprintf(debugFP, "ics_type %d\n", ics_type);
2316                 continue;
2317             }
2318             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2319                 ics_type = ICS_FICS;
2320                 ics_prefix = "$";
2321                 if (appData.debugMode)
2322                   fprintf(debugFP, "ics_type %d\n", ics_type);
2323                 continue;
2324             }
2325             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2326                 ics_type = ICS_CHESSNET;
2327                 ics_prefix = "/";
2328                 if (appData.debugMode)
2329                   fprintf(debugFP, "ics_type %d\n", ics_type);
2330                 continue;
2331             }
2332
2333             if (!loggedOn &&
2334                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2335                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2336                  looking_at(buf, &i, "will be \"*\""))) {
2337               strcpy(ics_handle, star_match[0]);
2338               continue;
2339             }
2340
2341             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2342               char buf[MSG_SIZ];
2343               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2344               DisplayIcsInteractionTitle(buf);
2345               have_set_title = TRUE;
2346             }
2347
2348             /* skip finger notes */
2349             if (started == STARTED_NONE &&
2350                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2351                  (buf[i] == '1' && buf[i+1] == '0')) &&
2352                 buf[i+2] == ':' && buf[i+3] == ' ') {
2353               started = STARTED_CHATTER;
2354               i += 3;
2355               continue;
2356             }
2357
2358             /* skip formula vars */
2359             if (started == STARTED_NONE &&
2360                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2361               started = STARTED_CHATTER;
2362               i += 3;
2363               continue;
2364             }
2365
2366             oldi = i;
2367             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2368             if (appData.autoKibitz && started == STARTED_NONE && 
2369                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2370                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2371                 if(looking_at(buf, &i, "* kibitzes: ") &&
2372                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2373                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2374                         suppressKibitz = TRUE;
2375                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2376                                 && (gameMode == IcsPlayingWhite)) ||
2377                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2378                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2379                             started = STARTED_CHATTER; // own kibitz we simply discard
2380                         else {
2381                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2382                             parse_pos = 0; parse[0] = NULLCHAR;
2383                             savingComment = TRUE;
2384                             suppressKibitz = gameMode != IcsObserving ? 2 :
2385                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2386                         } 
2387                         continue;
2388                 } else
2389                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2390                     started = STARTED_CHATTER;
2391                     suppressKibitz = TRUE;
2392                 }
2393             } // [HGM] kibitz: end of patch
2394
2395 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2396
2397             // [HGM] chat: intercept tells by users for which we have an open chat window
2398             channel = -1;
2399             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2400                                            looking_at(buf, &i, "* whispers:") ||
2401                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2402                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2403                 int p;
2404                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2405                 chattingPartner = -1;
2406
2407                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2408                 for(p=0; p<MAX_CHAT; p++) {
2409                     if(channel == atoi(chatPartner[p])) {
2410                     talker[0] = '['; strcat(talker, "]");
2411                     chattingPartner = p; break;
2412                     }
2413                 } else
2414                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2415                 for(p=0; p<MAX_CHAT; p++) {
2416                     if(!strcmp("WHISPER", chatPartner[p])) {
2417                         talker[0] = '['; strcat(talker, "]");
2418                         chattingPartner = p; break;
2419                     }
2420                 }
2421                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2422                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2423                     talker[0] = 0;
2424                     chattingPartner = p; break;
2425                 }
2426                 if(chattingPartner<0) i = oldi; else {
2427                     started = STARTED_COMMENT;
2428                     parse_pos = 0; parse[0] = NULLCHAR;
2429                     savingComment = TRUE;
2430                     suppressKibitz = TRUE;
2431                 }
2432             } // [HGM] chat: end of patch
2433
2434             if (appData.zippyTalk || appData.zippyPlay) {
2435                 /* [DM] Backup address for color zippy lines */
2436                 backup = i;
2437 #if ZIPPY
2438        #ifdef WIN32
2439                if (loggedOn == TRUE)
2440                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2441                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2442        #else
2443                 if (ZippyControl(buf, &i) ||
2444                     ZippyConverse(buf, &i) ||
2445                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2446                       loggedOn = TRUE;
2447                       if (!appData.colorize) continue;
2448                 }
2449        #endif
2450 #endif
2451             } // [DM] 'else { ' deleted
2452                 if (
2453                     /* Regular tells and says */
2454                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2455                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2456                     looking_at(buf, &i, "* says: ") ||
2457                     /* Don't color "message" or "messages" output */
2458                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2459                     looking_at(buf, &i, "*. * at *:*: ") ||
2460                     looking_at(buf, &i, "--* (*:*): ") ||
2461                     /* Message notifications (same color as tells) */
2462                     looking_at(buf, &i, "* has left a message ") ||
2463                     looking_at(buf, &i, "* just sent you a message:\n") ||
2464                     /* Whispers and kibitzes */
2465                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2466                     looking_at(buf, &i, "* kibitzes: ") ||
2467                     /* Channel tells */
2468                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2469
2470                   if (tkind == 1 && strchr(star_match[0], ':')) {
2471                       /* Avoid "tells you:" spoofs in channels */
2472                      tkind = 3;
2473                   }
2474                   if (star_match[0][0] == NULLCHAR ||
2475                       strchr(star_match[0], ' ') ||
2476                       (tkind == 3 && strchr(star_match[1], ' '))) {
2477                     /* Reject bogus matches */
2478                     i = oldi;
2479                   } else {
2480                     if (appData.colorize) {
2481                       if (oldi > next_out) {
2482                         SendToPlayer(&buf[next_out], oldi - next_out);
2483                         next_out = oldi;
2484                       }
2485                       switch (tkind) {
2486                       case 1:
2487                         Colorize(ColorTell, FALSE);
2488                         curColor = ColorTell;
2489                         break;
2490                       case 2:
2491                         Colorize(ColorKibitz, FALSE);
2492                         curColor = ColorKibitz;
2493                         break;
2494                       case 3:
2495                         p = strrchr(star_match[1], '(');
2496                         if (p == NULL) {
2497                           p = star_match[1];
2498                         } else {
2499                           p++;
2500                         }
2501                         if (atoi(p) == 1) {
2502                           Colorize(ColorChannel1, FALSE);
2503                           curColor = ColorChannel1;
2504                         } else {
2505                           Colorize(ColorChannel, FALSE);
2506                           curColor = ColorChannel;
2507                         }
2508                         break;
2509                       case 5:
2510                         curColor = ColorNormal;
2511                         break;
2512                       }
2513                     }
2514                     if (started == STARTED_NONE && appData.autoComment &&
2515                         (gameMode == IcsObserving ||
2516                          gameMode == IcsPlayingWhite ||
2517                          gameMode == IcsPlayingBlack)) {
2518                       parse_pos = i - oldi;
2519                       memcpy(parse, &buf[oldi], parse_pos);
2520                       parse[parse_pos] = NULLCHAR;
2521                       started = STARTED_COMMENT;
2522                       savingComment = TRUE;
2523                     } else {
2524                       started = STARTED_CHATTER;
2525                       savingComment = FALSE;
2526                     }
2527                     loggedOn = TRUE;
2528                     continue;
2529                   }
2530                 }
2531
2532                 if (looking_at(buf, &i, "* s-shouts: ") ||
2533                     looking_at(buf, &i, "* c-shouts: ")) {
2534                     if (appData.colorize) {
2535                         if (oldi > next_out) {
2536                             SendToPlayer(&buf[next_out], oldi - next_out);
2537                             next_out = oldi;
2538                         }
2539                         Colorize(ColorSShout, FALSE);
2540                         curColor = ColorSShout;
2541                     }
2542                     loggedOn = TRUE;
2543                     started = STARTED_CHATTER;
2544                     continue;
2545                 }
2546
2547                 if (looking_at(buf, &i, "--->")) {
2548                     loggedOn = TRUE;
2549                     continue;
2550                 }
2551
2552                 if (looking_at(buf, &i, "* shouts: ") ||
2553                     looking_at(buf, &i, "--> ")) {
2554                     if (appData.colorize) {
2555                         if (oldi > next_out) {
2556                             SendToPlayer(&buf[next_out], oldi - next_out);
2557                             next_out = oldi;
2558                         }
2559                         Colorize(ColorShout, FALSE);
2560                         curColor = ColorShout;
2561                     }
2562                     loggedOn = TRUE;
2563                     started = STARTED_CHATTER;
2564                     continue;
2565                 }
2566
2567                 if (looking_at( buf, &i, "Challenge:")) {
2568                     if (appData.colorize) {
2569                         if (oldi > next_out) {
2570                             SendToPlayer(&buf[next_out], oldi - next_out);
2571                             next_out = oldi;
2572                         }
2573                         Colorize(ColorChallenge, FALSE);
2574                         curColor = ColorChallenge;
2575                     }
2576                     loggedOn = TRUE;
2577                     continue;
2578                 }
2579
2580                 if (looking_at(buf, &i, "* offers you") ||
2581                     looking_at(buf, &i, "* offers to be") ||
2582                     looking_at(buf, &i, "* would like to") ||
2583                     looking_at(buf, &i, "* requests to") ||
2584                     looking_at(buf, &i, "Your opponent offers") ||
2585                     looking_at(buf, &i, "Your opponent requests")) {
2586
2587                     if (appData.colorize) {
2588                         if (oldi > next_out) {
2589                             SendToPlayer(&buf[next_out], oldi - next_out);
2590                             next_out = oldi;
2591                         }
2592                         Colorize(ColorRequest, FALSE);
2593                         curColor = ColorRequest;
2594                     }
2595                     continue;
2596                 }
2597
2598                 if (looking_at(buf, &i, "* (*) seeking")) {
2599                     if (appData.colorize) {
2600                         if (oldi > next_out) {
2601                             SendToPlayer(&buf[next_out], oldi - next_out);
2602                             next_out = oldi;
2603                         }
2604                         Colorize(ColorSeek, FALSE);
2605                         curColor = ColorSeek;
2606                     }
2607                     continue;
2608             }
2609
2610             if (looking_at(buf, &i, "\\   ")) {
2611                 if (prevColor != ColorNormal) {
2612                     if (oldi > next_out) {
2613                         SendToPlayer(&buf[next_out], oldi - next_out);
2614                         next_out = oldi;
2615                     }
2616                     Colorize(prevColor, TRUE);
2617                     curColor = prevColor;
2618                 }
2619                 if (savingComment) {
2620                     parse_pos = i - oldi;
2621                     memcpy(parse, &buf[oldi], parse_pos);
2622                     parse[parse_pos] = NULLCHAR;
2623                     started = STARTED_COMMENT;
2624                 } else {
2625                     started = STARTED_CHATTER;
2626                 }
2627                 continue;
2628             }
2629
2630             if (looking_at(buf, &i, "Black Strength :") ||
2631                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2632                 looking_at(buf, &i, "<10>") ||
2633                 looking_at(buf, &i, "#@#")) {
2634                 /* Wrong board style */
2635                 loggedOn = TRUE;
2636                 SendToICS(ics_prefix);
2637                 SendToICS("set style 12\n");
2638                 SendToICS(ics_prefix);
2639                 SendToICS("refresh\n");
2640                 continue;
2641             }
2642             
2643             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2644                 ICSInitScript();
2645                 have_sent_ICS_logon = 1;
2646                 continue;
2647             }
2648               
2649             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2650                 (looking_at(buf, &i, "\n<12> ") ||
2651                  looking_at(buf, &i, "<12> "))) {
2652                 loggedOn = TRUE;
2653                 if (oldi > next_out) {
2654                     SendToPlayer(&buf[next_out], oldi - next_out);
2655                 }
2656                 next_out = i;
2657                 started = STARTED_BOARD;
2658                 parse_pos = 0;
2659                 continue;
2660             }
2661
2662             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2663                 looking_at(buf, &i, "<b1> ")) {
2664                 if (oldi > next_out) {
2665                     SendToPlayer(&buf[next_out], oldi - next_out);
2666                 }
2667                 next_out = i;
2668                 started = STARTED_HOLDINGS;
2669                 parse_pos = 0;
2670                 continue;
2671             }
2672
2673             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2674                 loggedOn = TRUE;
2675                 /* Header for a move list -- first line */
2676
2677                 switch (ics_getting_history) {
2678                   case H_FALSE:
2679                     switch (gameMode) {
2680                       case IcsIdle:
2681                       case BeginningOfGame:
2682                         /* User typed "moves" or "oldmoves" while we
2683                            were idle.  Pretend we asked for these
2684                            moves and soak them up so user can step
2685                            through them and/or save them.
2686                            */
2687                         Reset(FALSE, TRUE);
2688                         gameMode = IcsObserving;
2689                         ModeHighlight();
2690                         ics_gamenum = -1;
2691                         ics_getting_history = H_GOT_UNREQ_HEADER;
2692                         break;
2693                       case EditGame: /*?*/
2694                       case EditPosition: /*?*/
2695                         /* Should above feature work in these modes too? */
2696                         /* For now it doesn't */
2697                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2698                         break;
2699                       default:
2700                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2701                         break;
2702                     }
2703                     break;
2704                   case H_REQUESTED:
2705                     /* Is this the right one? */
2706                     if (gameInfo.white && gameInfo.black &&
2707                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2708                         strcmp(gameInfo.black, star_match[2]) == 0) {
2709                         /* All is well */
2710                         ics_getting_history = H_GOT_REQ_HEADER;
2711                     }
2712                     break;
2713                   case H_GOT_REQ_HEADER:
2714                   case H_GOT_UNREQ_HEADER:
2715                   case H_GOT_UNWANTED_HEADER:
2716                   case H_GETTING_MOVES:
2717                     /* Should not happen */
2718                     DisplayError(_("Error gathering move list: two headers"), 0);
2719                     ics_getting_history = H_FALSE;
2720                     break;
2721                 }
2722
2723                 /* Save player ratings into gameInfo if needed */
2724                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2725                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2726                     (gameInfo.whiteRating == -1 ||
2727                      gameInfo.blackRating == -1)) {
2728
2729                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2730                     gameInfo.blackRating = string_to_rating(star_match[3]);
2731                     if (appData.debugMode)
2732                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2733                               gameInfo.whiteRating, gameInfo.blackRating);
2734                 }
2735                 continue;
2736             }
2737
2738             if (looking_at(buf, &i,
2739               "* * match, initial time: * minute*, increment: * second")) {
2740                 /* Header for a move list -- second line */
2741                 /* Initial board will follow if this is a wild game */
2742                 if (gameInfo.event != NULL) free(gameInfo.event);
2743                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2744                 gameInfo.event = StrSave(str);
2745                 /* [HGM] we switched variant. Translate boards if needed. */
2746                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2747                 continue;
2748             }
2749
2750             if (looking_at(buf, &i, "Move  ")) {
2751                 /* Beginning of a move list */
2752                 switch (ics_getting_history) {
2753                   case H_FALSE:
2754                     /* Normally should not happen */
2755                     /* Maybe user hit reset while we were parsing */
2756                     break;
2757                   case H_REQUESTED:
2758                     /* Happens if we are ignoring a move list that is not
2759                      * the one we just requested.  Common if the user
2760                      * tries to observe two games without turning off
2761                      * getMoveList */
2762                     break;
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: nested"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                   case H_GOT_REQ_HEADER:
2769                     ics_getting_history = H_GETTING_MOVES;
2770                     started = STARTED_MOVES;
2771                     parse_pos = 0;
2772                     if (oldi > next_out) {
2773                         SendToPlayer(&buf[next_out], oldi - next_out);
2774                     }
2775                     break;
2776                   case H_GOT_UNREQ_HEADER:
2777                     ics_getting_history = H_GETTING_MOVES;
2778                     started = STARTED_MOVES_NOHIDE;
2779                     parse_pos = 0;
2780                     break;
2781                   case H_GOT_UNWANTED_HEADER:
2782                     ics_getting_history = H_FALSE;
2783                     break;
2784                 }
2785                 continue;
2786             }                           
2787             
2788             if (looking_at(buf, &i, "% ") ||
2789                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2790                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2791                 savingComment = FALSE;
2792                 switch (started) {
2793                   case STARTED_MOVES:
2794                   case STARTED_MOVES_NOHIDE:
2795                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2796                     parse[parse_pos + i - oldi] = NULLCHAR;
2797                     ParseGameHistory(parse);
2798 #if ZIPPY
2799                     if (appData.zippyPlay && first.initDone) {
2800                         FeedMovesToProgram(&first, forwardMostMove);
2801                         if (gameMode == IcsPlayingWhite) {
2802                             if (WhiteOnMove(forwardMostMove)) {
2803                                 if (first.sendTime) {
2804                                   if (first.useColors) {
2805                                     SendToProgram("black\n", &first); 
2806                                   }
2807                                   SendTimeRemaining(&first, TRUE);
2808                                 }
2809                                 if (first.useColors) {
2810                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2811                                 }
2812                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2813                                 first.maybeThinking = TRUE;
2814                             } else {
2815                                 if (first.usePlayother) {
2816                                   if (first.sendTime) {
2817                                     SendTimeRemaining(&first, TRUE);
2818                                   }
2819                                   SendToProgram("playother\n", &first);
2820                                   firstMove = FALSE;
2821                                 } else {
2822                                   firstMove = TRUE;
2823                                 }
2824                             }
2825                         } else if (gameMode == IcsPlayingBlack) {
2826                             if (!WhiteOnMove(forwardMostMove)) {
2827                                 if (first.sendTime) {
2828                                   if (first.useColors) {
2829                                     SendToProgram("white\n", &first);
2830                                   }
2831                                   SendTimeRemaining(&first, FALSE);
2832                                 }
2833                                 if (first.useColors) {
2834                                   SendToProgram("black\n", &first);
2835                                 }
2836                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2837                                 first.maybeThinking = TRUE;
2838                             } else {
2839                                 if (first.usePlayother) {
2840                                   if (first.sendTime) {
2841                                     SendTimeRemaining(&first, FALSE);
2842                                   }
2843                                   SendToProgram("playother\n", &first);
2844                                   firstMove = FALSE;
2845                                 } else {
2846                                   firstMove = TRUE;
2847                                 }
2848                             }
2849                         }                       
2850                     }
2851 #endif
2852                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2853                         /* Moves came from oldmoves or moves command
2854                            while we weren't doing anything else.
2855                            */
2856                         currentMove = forwardMostMove;
2857                         ClearHighlights();/*!!could figure this out*/
2858                         flipView = appData.flipView;
2859                         DrawPosition(FALSE, boards[currentMove]);
2860                         DisplayBothClocks();
2861                         sprintf(str, "%s vs. %s",
2862                                 gameInfo.white, gameInfo.black);
2863                         DisplayTitle(str);
2864                         gameMode = IcsIdle;
2865                     } else {
2866                         /* Moves were history of an active game */
2867                         if (gameInfo.resultDetails != NULL) {
2868                             free(gameInfo.resultDetails);
2869                             gameInfo.resultDetails = NULL;
2870                         }
2871                     }
2872                     HistorySet(parseList, backwardMostMove,
2873                                forwardMostMove, currentMove-1);
2874                     DisplayMove(currentMove - 1);
2875                     if (started == STARTED_MOVES) next_out = i;
2876                     started = STARTED_NONE;
2877                     ics_getting_history = H_FALSE;
2878                     break;
2879
2880                   case STARTED_OBSERVE:
2881                     started = STARTED_NONE;
2882                     SendToICS(ics_prefix);
2883                     SendToICS("refresh\n");
2884                     break;
2885
2886                   default:
2887                     break;
2888                 }
2889                 if(bookHit) { // [HGM] book: simulate book reply
2890                     static char bookMove[MSG_SIZ]; // a bit generous?
2891
2892                     programStats.nodes = programStats.depth = programStats.time = 
2893                     programStats.score = programStats.got_only_move = 0;
2894                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2895
2896                     strcpy(bookMove, "move ");
2897                     strcat(bookMove, bookHit);
2898                     HandleMachineMove(bookMove, &first);
2899                 }
2900                 continue;
2901             }
2902             
2903             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2904                  started == STARTED_HOLDINGS ||
2905                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2906                 /* Accumulate characters in move list or board */
2907                 parse[parse_pos++] = buf[i];
2908             }
2909             
2910             /* Start of game messages.  Mostly we detect start of game
2911                when the first board image arrives.  On some versions
2912                of the ICS, though, we need to do a "refresh" after starting
2913                to observe in order to get the current board right away. */
2914             if (looking_at(buf, &i, "Adding game * to observation list")) {
2915                 started = STARTED_OBSERVE;
2916                 continue;
2917             }
2918
2919             /* Handle auto-observe */
2920             if (appData.autoObserve &&
2921                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2922                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2923                 char *player;
2924                 /* Choose the player that was highlighted, if any. */
2925                 if (star_match[0][0] == '\033' ||
2926                     star_match[1][0] != '\033') {
2927                     player = star_match[0];
2928                 } else {
2929                     player = star_match[2];
2930                 }
2931                 sprintf(str, "%sobserve %s\n",
2932                         ics_prefix, StripHighlightAndTitle(player));
2933                 SendToICS(str);
2934
2935                 /* Save ratings from notify string */
2936                 strcpy(player1Name, star_match[0]);
2937                 player1Rating = string_to_rating(star_match[1]);
2938                 strcpy(player2Name, star_match[2]);
2939                 player2Rating = string_to_rating(star_match[3]);
2940
2941                 if (appData.debugMode)
2942                   fprintf(debugFP, 
2943                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2944                           player1Name, player1Rating,
2945                           player2Name, player2Rating);
2946
2947                 continue;
2948             }
2949
2950             /* Deal with automatic examine mode after a game,
2951                and with IcsObserving -> IcsExamining transition */
2952             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2953                 looking_at(buf, &i, "has made you an examiner of game *")) {
2954
2955                 int gamenum = atoi(star_match[0]);
2956                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2957                     gamenum == ics_gamenum) {
2958                     /* We were already playing or observing this game;
2959                        no need to refetch history */
2960                     gameMode = IcsExamining;
2961                     if (pausing) {
2962                         pauseExamForwardMostMove = forwardMostMove;
2963                     } else if (currentMove < forwardMostMove) {
2964                         ForwardInner(forwardMostMove);
2965                     }
2966                 } else {
2967                     /* I don't think this case really can happen */
2968                     SendToICS(ics_prefix);
2969                     SendToICS("refresh\n");
2970                 }
2971                 continue;
2972             }    
2973             
2974             /* Error messages */
2975 //          if (ics_user_moved) {
2976             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2977                 if (looking_at(buf, &i, "Illegal move") ||
2978                     looking_at(buf, &i, "Not a legal move") ||
2979                     looking_at(buf, &i, "Your king is in check") ||
2980                     looking_at(buf, &i, "It isn't your turn") ||
2981                     looking_at(buf, &i, "It is not your move")) {
2982                     /* Illegal move */
2983                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2984                         currentMove = --forwardMostMove;
2985                         DisplayMove(currentMove - 1); /* before DMError */
2986                         DrawPosition(FALSE, boards[currentMove]);
2987                         SwitchClocks();
2988                         DisplayBothClocks();
2989                     }
2990                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2991                     ics_user_moved = 0;
2992                     continue;
2993                 }
2994             }
2995
2996             if (looking_at(buf, &i, "still have time") ||
2997                 looking_at(buf, &i, "not out of time") ||
2998                 looking_at(buf, &i, "either player is out of time") ||
2999                 looking_at(buf, &i, "has timeseal; checking")) {
3000                 /* We must have called his flag a little too soon */
3001                 whiteFlag = blackFlag = FALSE;
3002                 continue;
3003             }
3004
3005             if (looking_at(buf, &i, "added * seconds to") ||
3006                 looking_at(buf, &i, "seconds were added to")) {
3007                 /* Update the clocks */
3008                 SendToICS(ics_prefix);
3009                 SendToICS("refresh\n");
3010                 continue;
3011             }
3012
3013             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3014                 ics_clock_paused = TRUE;
3015                 StopClocks();
3016                 continue;
3017             }
3018
3019             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3020                 ics_clock_paused = FALSE;
3021                 StartClocks();
3022                 continue;
3023             }
3024
3025             /* Grab player ratings from the Creating: message.
3026                Note we have to check for the special case when
3027                the ICS inserts things like [white] or [black]. */
3028             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3029                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3030                 /* star_matches:
3031                    0    player 1 name (not necessarily white)
3032                    1    player 1 rating
3033                    2    empty, white, or black (IGNORED)
3034                    3    player 2 name (not necessarily black)
3035                    4    player 2 rating
3036                    
3037                    The names/ratings are sorted out when the game
3038                    actually starts (below).
3039                 */
3040                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3041                 player1Rating = string_to_rating(star_match[1]);
3042                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3043                 player2Rating = string_to_rating(star_match[4]);
3044
3045                 if (appData.debugMode)
3046                   fprintf(debugFP, 
3047                           "Ratings from 'Creating:' %s %d, %s %d\n",
3048                           player1Name, player1Rating,
3049                           player2Name, player2Rating);
3050
3051                 continue;
3052             }
3053             
3054             /* Improved generic start/end-of-game messages */
3055             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3056                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3057                 /* If tkind == 0: */
3058                 /* star_match[0] is the game number */
3059                 /*           [1] is the white player's name */
3060                 /*           [2] is the black player's name */
3061                 /* For end-of-game: */
3062                 /*           [3] is the reason for the game end */
3063                 /*           [4] is a PGN end game-token, preceded by " " */
3064                 /* For start-of-game: */
3065                 /*           [3] begins with "Creating" or "Continuing" */
3066                 /*           [4] is " *" or empty (don't care). */
3067                 int gamenum = atoi(star_match[0]);
3068                 char *whitename, *blackname, *why, *endtoken;
3069                 ChessMove endtype = (ChessMove) 0;
3070
3071                 if (tkind == 0) {
3072                   whitename = star_match[1];
3073                   blackname = star_match[2];
3074                   why = star_match[3];
3075                   endtoken = star_match[4];
3076                 } else {
3077                   whitename = star_match[1];
3078                   blackname = star_match[3];
3079                   why = star_match[5];
3080                   endtoken = star_match[6];
3081                 }
3082
3083                 /* Game start messages */
3084                 if (strncmp(why, "Creating ", 9) == 0 ||
3085                     strncmp(why, "Continuing ", 11) == 0) {
3086                     gs_gamenum = gamenum;
3087                     strcpy(gs_kind, strchr(why, ' ') + 1);
3088 #if ZIPPY
3089                     if (appData.zippyPlay) {
3090                         ZippyGameStart(whitename, blackname);
3091                     }
3092 #endif /*ZIPPY*/
3093                     continue;
3094                 }
3095
3096                 /* Game end messages */
3097                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3098                     ics_gamenum != gamenum) {
3099                     continue;
3100                 }
3101                 while (endtoken[0] == ' ') endtoken++;
3102                 switch (endtoken[0]) {
3103                   case '*':
3104                   default:
3105                     endtype = GameUnfinished;
3106                     break;
3107                   case '0':
3108                     endtype = BlackWins;
3109                     break;
3110                   case '1':
3111                     if (endtoken[1] == '/')
3112                       endtype = GameIsDrawn;
3113                     else
3114                       endtype = WhiteWins;
3115                     break;
3116                 }
3117                 GameEnds(endtype, why, GE_ICS);
3118 #if ZIPPY
3119                 if (appData.zippyPlay && first.initDone) {
3120                     ZippyGameEnd(endtype, why);
3121                     if (first.pr == NULL) {
3122                       /* Start the next process early so that we'll
3123                          be ready for the next challenge */
3124                       StartChessProgram(&first);
3125                     }
3126                     /* Send "new" early, in case this command takes
3127                        a long time to finish, so that we'll be ready
3128                        for the next challenge. */
3129                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3130                     Reset(TRUE, TRUE);
3131                 }
3132 #endif /*ZIPPY*/
3133                 continue;
3134             }
3135
3136             if (looking_at(buf, &i, "Removing game * from observation") ||
3137                 looking_at(buf, &i, "no longer observing game *") ||
3138                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3139                 if (gameMode == IcsObserving &&
3140                     atoi(star_match[0]) == ics_gamenum)
3141                   {
3142                       /* icsEngineAnalyze */
3143                       if (appData.icsEngineAnalyze) {
3144                             ExitAnalyzeMode();
3145                             ModeHighlight();
3146                       }
3147                       StopClocks();
3148                       gameMode = IcsIdle;
3149                       ics_gamenum = -1;
3150                       ics_user_moved = FALSE;
3151                   }
3152                 continue;
3153             }
3154
3155             if (looking_at(buf, &i, "no longer examining game *")) {
3156                 if (gameMode == IcsExamining &&
3157                     atoi(star_match[0]) == ics_gamenum)
3158                   {
3159                       gameMode = IcsIdle;
3160                       ics_gamenum = -1;
3161                       ics_user_moved = FALSE;
3162                   }
3163                 continue;
3164             }
3165
3166             /* Advance leftover_start past any newlines we find,
3167                so only partial lines can get reparsed */
3168             if (looking_at(buf, &i, "\n")) {
3169                 prevColor = curColor;
3170                 if (curColor != ColorNormal) {
3171                     if (oldi > next_out) {
3172                         SendToPlayer(&buf[next_out], oldi - next_out);
3173                         next_out = oldi;
3174                     }
3175                     Colorize(ColorNormal, FALSE);
3176                     curColor = ColorNormal;
3177                 }
3178                 if (started == STARTED_BOARD) {
3179                     started = STARTED_NONE;
3180                     parse[parse_pos] = NULLCHAR;
3181                     ParseBoard12(parse);
3182                     ics_user_moved = 0;
3183
3184                     /* Send premove here */
3185                     if (appData.premove) {
3186                       char str[MSG_SIZ];
3187                       if (currentMove == 0 &&
3188                           gameMode == IcsPlayingWhite &&
3189                           appData.premoveWhite) {
3190                         sprintf(str, "%s%s\n", ics_prefix,
3191                                 appData.premoveWhiteText);
3192                         if (appData.debugMode)
3193                           fprintf(debugFP, "Sending premove:\n");
3194                         SendToICS(str);
3195                       } else if (currentMove == 1 &&
3196                                  gameMode == IcsPlayingBlack &&
3197                                  appData.premoveBlack) {
3198                         sprintf(str, "%s%s\n", ics_prefix,
3199                                 appData.premoveBlackText);
3200                         if (appData.debugMode)
3201                           fprintf(debugFP, "Sending premove:\n");
3202                         SendToICS(str);
3203                       } else if (gotPremove) {
3204                         gotPremove = 0;
3205                         ClearPremoveHighlights();
3206                         if (appData.debugMode)
3207                           fprintf(debugFP, "Sending premove:\n");
3208                           UserMoveEvent(premoveFromX, premoveFromY, 
3209                                         premoveToX, premoveToY, 
3210                                         premovePromoChar);
3211                       }
3212                     }
3213
3214                     /* Usually suppress following prompt */
3215                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3216                         if (looking_at(buf, &i, "*% ")) {
3217                             savingComment = FALSE;
3218                         }
3219                     }
3220                     next_out = i;
3221                 } else if (started == STARTED_HOLDINGS) {
3222                     int gamenum;
3223                     char new_piece[MSG_SIZ];
3224                     started = STARTED_NONE;
3225                     parse[parse_pos] = NULLCHAR;
3226                     if (appData.debugMode)
3227                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3228                                                         parse, currentMove);
3229                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3230                         gamenum == ics_gamenum) {
3231                         if (gameInfo.variant == VariantNormal) {
3232                           /* [HGM] We seem to switch variant during a game!
3233                            * Presumably no holdings were displayed, so we have
3234                            * to move the position two files to the right to
3235                            * create room for them!
3236                            */
3237                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3238                           /* Get a move list just to see the header, which
3239                              will tell us whether this is really bug or zh */
3240                           if (ics_getting_history == H_FALSE) {
3241                             ics_getting_history = H_REQUESTED;
3242                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3243                             SendToICS(str);
3244                           }
3245                         }
3246                         new_piece[0] = NULLCHAR;
3247                         sscanf(parse, "game %d white [%s black [%s <- %s",
3248                                &gamenum, white_holding, black_holding,
3249                                new_piece);
3250                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3251                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3252                         /* [HGM] copy holdings to board holdings area */
3253                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3254                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3255 #if ZIPPY
3256                         if (appData.zippyPlay && first.initDone) {
3257                             ZippyHoldings(white_holding, black_holding,
3258                                           new_piece);
3259                         }
3260 #endif /*ZIPPY*/
3261                         if (tinyLayout || smallLayout) {
3262                             char wh[16], bh[16];
3263                             PackHolding(wh, white_holding);
3264                             PackHolding(bh, black_holding);
3265                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3266                                     gameInfo.white, gameInfo.black);
3267                         } else {
3268                             sprintf(str, "%s [%s] vs. %s [%s]",
3269                                     gameInfo.white, white_holding,
3270                                     gameInfo.black, black_holding);
3271                         }
3272
3273                         DrawPosition(FALSE, boards[currentMove]);
3274                         DisplayTitle(str);
3275                     }
3276                     /* Suppress following prompt */
3277                     if (looking_at(buf, &i, "*% ")) {
3278                         savingComment = FALSE;
3279                     }
3280                     next_out = i;
3281                 }
3282                 continue;
3283             }
3284
3285             i++;                /* skip unparsed character and loop back */
3286         }
3287         
3288         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3289             started != STARTED_HOLDINGS && i > next_out) {
3290             SendToPlayer(&buf[next_out], i - next_out);
3291             next_out = i;
3292         }
3293         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3294         
3295         leftover_len = buf_len - leftover_start;
3296         /* if buffer ends with something we couldn't parse,
3297            reparse it after appending the next read */
3298         
3299     } else if (count == 0) {
3300         RemoveInputSource(isr);
3301         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3302     } else {
3303         DisplayFatalError(_("Error reading from ICS"), error, 1);
3304     }
3305 }
3306
3307
3308 /* Board style 12 looks like this:
3309    
3310    <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
3311    
3312  * The "<12> " is stripped before it gets to this routine.  The two
3313  * trailing 0's (flip state and clock ticking) are later addition, and
3314  * some chess servers may not have them, or may have only the first.
3315  * Additional trailing fields may be added in the future.  
3316  */
3317
3318 #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"
3319
3320 #define RELATION_OBSERVING_PLAYED    0
3321 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3322 #define RELATION_PLAYING_MYMOVE      1
3323 #define RELATION_PLAYING_NOTMYMOVE  -1
3324 #define RELATION_EXAMINING           2
3325 #define RELATION_ISOLATED_BOARD     -3
3326 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3327
3328 void
3329 ParseBoard12(string)
3330      char *string;
3331
3332     GameMode newGameMode;
3333     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3334     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3335     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3336     char to_play, board_chars[200];
3337     char move_str[500], str[500], elapsed_time[500];
3338     char black[32], white[32];
3339     Board board;
3340     int prevMove = currentMove;
3341     int ticking = 2;
3342     ChessMove moveType;
3343     int fromX, fromY, toX, toY;
3344     char promoChar;
3345     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3346     char *bookHit = NULL; // [HGM] book
3347
3348     fromX = fromY = toX = toY = -1;
3349     
3350     newGame = FALSE;
3351
3352     if (appData.debugMode)
3353       fprintf(debugFP, _("Parsing board: %s\n"), string);
3354
3355     move_str[0] = NULLCHAR;
3356     elapsed_time[0] = NULLCHAR;
3357     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3358         int  i = 0, j;
3359         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3360             if(string[i] == ' ') { ranks++; files = 0; }
3361             else files++;
3362             i++;
3363         }
3364         for(j = 0; j <i; j++) board_chars[j] = string[j];
3365         board_chars[i] = '\0';
3366         string += i + 1;
3367     }
3368     n = sscanf(string, PATTERN, &to_play, &double_push,
3369                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3370                &gamenum, white, black, &relation, &basetime, &increment,
3371                &white_stren, &black_stren, &white_time, &black_time,
3372                &moveNum, str, elapsed_time, move_str, &ics_flip,
3373                &ticking);
3374
3375     if (n < 21) {
3376         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3377         DisplayError(str, 0);
3378         return;
3379     }
3380
3381     /* Convert the move number to internal form */
3382     moveNum = (moveNum - 1) * 2;
3383     if (to_play == 'B') moveNum++;
3384     if (moveNum >= MAX_MOVES) {
3385       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3386                         0, 1);
3387       return;
3388     }
3389     
3390     switch (relation) {
3391       case RELATION_OBSERVING_PLAYED:
3392       case RELATION_OBSERVING_STATIC:
3393         if (gamenum == -1) {
3394             /* Old ICC buglet */
3395             relation = RELATION_OBSERVING_STATIC;
3396         }
3397         newGameMode = IcsObserving;
3398         break;
3399       case RELATION_PLAYING_MYMOVE:
3400       case RELATION_PLAYING_NOTMYMOVE:
3401         newGameMode =
3402           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3403             IcsPlayingWhite : IcsPlayingBlack;
3404         break;
3405       case RELATION_EXAMINING:
3406         newGameMode = IcsExamining;
3407         break;
3408       case RELATION_ISOLATED_BOARD:
3409       default:
3410         /* Just display this board.  If user was doing something else,
3411            we will forget about it until the next board comes. */ 
3412         newGameMode = IcsIdle;
3413         break;
3414       case RELATION_STARTING_POSITION:
3415         newGameMode = gameMode;
3416         break;
3417     }
3418     
3419     /* Modify behavior for initial board display on move listing
3420        of wild games.
3421        */
3422     switch (ics_getting_history) {
3423       case H_FALSE:
3424       case H_REQUESTED:
3425         break;
3426       case H_GOT_REQ_HEADER:
3427       case H_GOT_UNREQ_HEADER:
3428         /* This is the initial position of the current game */
3429         gamenum = ics_gamenum;
3430         moveNum = 0;            /* old ICS bug workaround */
3431         if (to_play == 'B') {
3432           startedFromSetupPosition = TRUE;
3433           blackPlaysFirst = TRUE;
3434           moveNum = 1;
3435           if (forwardMostMove == 0) forwardMostMove = 1;
3436           if (backwardMostMove == 0) backwardMostMove = 1;
3437           if (currentMove == 0) currentMove = 1;
3438         }
3439         newGameMode = gameMode;
3440         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3441         break;
3442       case H_GOT_UNWANTED_HEADER:
3443         /* This is an initial board that we don't want */
3444         return;
3445       case H_GETTING_MOVES:
3446         /* Should not happen */
3447         DisplayError(_("Error gathering move list: extra board"), 0);
3448         ics_getting_history = H_FALSE;
3449         return;
3450     }
3451     
3452     /* Take action if this is the first board of a new game, or of a
3453        different game than is currently being displayed.  */
3454     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3455         relation == RELATION_ISOLATED_BOARD) {
3456         
3457         /* Forget the old game and get the history (if any) of the new one */
3458         if (gameMode != BeginningOfGame) {
3459           Reset(FALSE, TRUE);
3460         }
3461         newGame = TRUE;
3462         if (appData.autoRaiseBoard) BoardToTop();
3463         prevMove = -3;
3464         if (gamenum == -1) {
3465             newGameMode = IcsIdle;
3466         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3467                    appData.getMoveList) {
3468             /* Need to get game history */
3469             ics_getting_history = H_REQUESTED;
3470             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3471             SendToICS(str);
3472         }
3473         
3474         /* Initially flip the board to have black on the bottom if playing
3475            black or if the ICS flip flag is set, but let the user change
3476            it with the Flip View button. */
3477         flipView = appData.autoFlipView ? 
3478           (newGameMode == IcsPlayingBlack) || ics_flip :
3479           appData.flipView;
3480         
3481         /* Done with values from previous mode; copy in new ones */
3482         gameMode = newGameMode;
3483         ModeHighlight();
3484         ics_gamenum = gamenum;
3485         if (gamenum == gs_gamenum) {
3486             int klen = strlen(gs_kind);
3487             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3488             sprintf(str, "ICS %s", gs_kind);
3489             gameInfo.event = StrSave(str);
3490         } else {
3491             gameInfo.event = StrSave("ICS game");
3492         }
3493         gameInfo.site = StrSave(appData.icsHost);
3494         gameInfo.date = PGNDate();
3495         gameInfo.round = StrSave("-");
3496         gameInfo.white = StrSave(white);
3497         gameInfo.black = StrSave(black);
3498         timeControl = basetime * 60 * 1000;
3499         timeControl_2 = 0;
3500         timeIncrement = increment * 1000;
3501         movesPerSession = 0;
3502         gameInfo.timeControl = TimeControlTagValue();
3503         VariantSwitch(board, StringToVariant(gameInfo.event) );
3504   if (appData.debugMode) {
3505     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3506     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3507     setbuf(debugFP, NULL);
3508   }
3509
3510         gameInfo.outOfBook = NULL;
3511         
3512         /* Do we have the ratings? */
3513         if (strcmp(player1Name, white) == 0 &&
3514             strcmp(player2Name, black) == 0) {
3515             if (appData.debugMode)
3516               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3517                       player1Rating, player2Rating);
3518             gameInfo.whiteRating = player1Rating;
3519             gameInfo.blackRating = player2Rating;
3520         } else if (strcmp(player2Name, white) == 0 &&
3521                    strcmp(player1Name, black) == 0) {
3522             if (appData.debugMode)
3523               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3524                       player2Rating, player1Rating);
3525             gameInfo.whiteRating = player2Rating;
3526             gameInfo.blackRating = player1Rating;
3527         }
3528         player1Name[0] = player2Name[0] = NULLCHAR;
3529
3530         /* Silence shouts if requested */
3531         if (appData.quietPlay &&
3532             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3533             SendToICS(ics_prefix);
3534             SendToICS("set shout 0\n");
3535         }
3536     }
3537     
3538     /* Deal with midgame name changes */
3539     if (!newGame) {
3540         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3541             if (gameInfo.white) free(gameInfo.white);
3542             gameInfo.white = StrSave(white);
3543         }
3544         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3545             if (gameInfo.black) free(gameInfo.black);
3546             gameInfo.black = StrSave(black);
3547         }
3548     }
3549     
3550     /* Throw away game result if anything actually changes in examine mode */
3551     if (gameMode == IcsExamining && !newGame) {
3552         gameInfo.result = GameUnfinished;
3553         if (gameInfo.resultDetails != NULL) {
3554             free(gameInfo.resultDetails);
3555             gameInfo.resultDetails = NULL;
3556         }
3557     }
3558     
3559     /* In pausing && IcsExamining mode, we ignore boards coming
3560        in if they are in a different variation than we are. */
3561     if (pauseExamInvalid) return;
3562     if (pausing && gameMode == IcsExamining) {
3563         if (moveNum <= pauseExamForwardMostMove) {
3564             pauseExamInvalid = TRUE;
3565             forwardMostMove = pauseExamForwardMostMove;
3566             return;
3567         }
3568     }
3569     
3570   if (appData.debugMode) {
3571     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3572   }
3573     /* Parse the board */
3574     for (k = 0; k < ranks; k++) {
3575       for (j = 0; j < files; j++)
3576         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3577       if(gameInfo.holdingsWidth > 1) {
3578            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3579            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3580       }
3581     }
3582     CopyBoard(boards[moveNum], board);
3583     if (moveNum == 0) {
3584         startedFromSetupPosition =
3585           !CompareBoards(board, initialPosition);
3586         if(startedFromSetupPosition)
3587             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3588     }
3589
3590     /* [HGM] Set castling rights. Take the outermost Rooks,
3591        to make it also work for FRC opening positions. Note that board12
3592        is really defective for later FRC positions, as it has no way to
3593        indicate which Rook can castle if they are on the same side of King.
3594        For the initial position we grant rights to the outermost Rooks,
3595        and remember thos rights, and we then copy them on positions
3596        later in an FRC game. This means WB might not recognize castlings with
3597        Rooks that have moved back to their original position as illegal,
3598        but in ICS mode that is not its job anyway.
3599     */
3600     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3601     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3602
3603         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3604             if(board[0][i] == WhiteRook) j = i;
3605         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3606         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3607             if(board[0][i] == WhiteRook) j = i;
3608         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3609         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3611         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3612         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3613             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3614         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3615
3616         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3618             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3619         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3620             if(board[BOARD_HEIGHT-1][k] == bKing)
3621                 initialRights[5] = castlingRights[moveNum][5] = k;
3622     } else { int r;
3623         r = castlingRights[moveNum][0] = initialRights[0];
3624         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3625         r = castlingRights[moveNum][1] = initialRights[1];
3626         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3627         r = castlingRights[moveNum][3] = initialRights[3];
3628         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3629         r = castlingRights[moveNum][4] = initialRights[4];
3630         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3631         /* wildcastle kludge: always assume King has rights */
3632         r = castlingRights[moveNum][2] = initialRights[2];
3633         r = castlingRights[moveNum][5] = initialRights[5];
3634     }
3635     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3636     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3637
3638     
3639     if (ics_getting_history == H_GOT_REQ_HEADER ||
3640         ics_getting_history == H_GOT_UNREQ_HEADER) {
3641         /* This was an initial position from a move list, not
3642            the current position */
3643         return;
3644     }
3645     
3646     /* Update currentMove and known move number limits */
3647     newMove = newGame || moveNum > forwardMostMove;
3648
3649     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3650     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3651         takeback = forwardMostMove - moveNum;
3652         for (i = 0; i < takeback; i++) {
3653              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3654              SendToProgram("undo\n", &first);
3655         }
3656     }
3657
3658     if (newGame) {
3659         forwardMostMove = backwardMostMove = currentMove = moveNum;
3660         if (gameMode == IcsExamining && moveNum == 0) {
3661           /* Workaround for ICS limitation: we are not told the wild
3662              type when starting to examine a game.  But if we ask for
3663              the move list, the move list header will tell us */
3664             ics_getting_history = H_REQUESTED;
3665             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3666             SendToICS(str);
3667         }
3668     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3669                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3670         forwardMostMove = moveNum;
3671         if (!pausing || currentMove > forwardMostMove)
3672           currentMove = forwardMostMove;
3673     } else {
3674         /* New part of history that is not contiguous with old part */ 
3675         if (pausing && gameMode == IcsExamining) {
3676             pauseExamInvalid = TRUE;
3677             forwardMostMove = pauseExamForwardMostMove;
3678             return;
3679         }
3680         forwardMostMove = backwardMostMove = currentMove = moveNum;
3681         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3682             ics_getting_history = H_REQUESTED;
3683             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3684             SendToICS(str);
3685         }
3686     }
3687     
3688     /* Update the clocks */
3689     if (strchr(elapsed_time, '.')) {
3690       /* Time is in ms */
3691       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3692       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3693     } else {
3694       /* Time is in seconds */
3695       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3696       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3697     }
3698       
3699
3700 #if ZIPPY
3701     if (appData.zippyPlay && newGame &&
3702         gameMode != IcsObserving && gameMode != IcsIdle &&
3703         gameMode != IcsExamining)
3704       ZippyFirstBoard(moveNum, basetime, increment);
3705 #endif
3706     
3707     /* Put the move on the move list, first converting
3708        to canonical algebraic form. */
3709     if (moveNum > 0) {
3710   if (appData.debugMode) {
3711     if (appData.debugMode) { int f = forwardMostMove;
3712         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3713                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3714     }
3715     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3716     fprintf(debugFP, "moveNum = %d\n", moveNum);
3717     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3718     setbuf(debugFP, NULL);
3719   }
3720         if (moveNum <= backwardMostMove) {
3721             /* We don't know what the board looked like before
3722                this move.  Punt. */
3723             strcpy(parseList[moveNum - 1], move_str);
3724             strcat(parseList[moveNum - 1], " ");
3725             strcat(parseList[moveNum - 1], elapsed_time);
3726             moveList[moveNum - 1][0] = NULLCHAR;
3727         } else if (strcmp(move_str, "none") == 0) {
3728             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3729             /* Again, we don't know what the board looked like;
3730                this is really the start of the game. */
3731             parseList[moveNum - 1][0] = NULLCHAR;
3732             moveList[moveNum - 1][0] = NULLCHAR;
3733             backwardMostMove = moveNum;
3734             startedFromSetupPosition = TRUE;
3735             fromX = fromY = toX = toY = -1;
3736         } else {
3737           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3738           //                 So we parse the long-algebraic move string in stead of the SAN move
3739           int valid; char buf[MSG_SIZ], *prom;
3740
3741           // str looks something like "Q/a1-a2"; kill the slash
3742           if(str[1] == '/') 
3743                 sprintf(buf, "%c%s", str[0], str+2);
3744           else  strcpy(buf, str); // might be castling
3745           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3746                 strcat(buf, prom); // long move lacks promo specification!
3747           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3748                 if(appData.debugMode) 
3749                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3750                 strcpy(move_str, buf);
3751           }
3752           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3753                                 &fromX, &fromY, &toX, &toY, &promoChar)
3754                || ParseOneMove(buf, moveNum - 1, &moveType,
3755                                 &fromX, &fromY, &toX, &toY, &promoChar);
3756           // end of long SAN patch
3757           if (valid) {
3758             (void) CoordsToAlgebraic(boards[moveNum - 1],
3759                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3760                                      fromY, fromX, toY, toX, promoChar,
3761                                      parseList[moveNum-1]);
3762             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3763                              castlingRights[moveNum]) ) {
3764               case MT_NONE:
3765               case MT_STALEMATE:
3766               default:
3767                 break;
3768               case MT_CHECK:
3769                 if(gameInfo.variant != VariantShogi)
3770                     strcat(parseList[moveNum - 1], "+");
3771                 break;
3772               case MT_CHECKMATE:
3773               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3774                 strcat(parseList[moveNum - 1], "#");
3775                 break;
3776             }
3777             strcat(parseList[moveNum - 1], " ");
3778             strcat(parseList[moveNum - 1], elapsed_time);
3779             /* currentMoveString is set as a side-effect of ParseOneMove */
3780             strcpy(moveList[moveNum - 1], currentMoveString);
3781             strcat(moveList[moveNum - 1], "\n");
3782           } else {
3783             /* Move from ICS was illegal!?  Punt. */
3784   if (appData.debugMode) {
3785     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3786     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3787   }
3788             strcpy(parseList[moveNum - 1], move_str);
3789             strcat(parseList[moveNum - 1], " ");
3790             strcat(parseList[moveNum - 1], elapsed_time);
3791             moveList[moveNum - 1][0] = NULLCHAR;
3792             fromX = fromY = toX = toY = -1;
3793           }
3794         }
3795   if (appData.debugMode) {
3796     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3797     setbuf(debugFP, NULL);
3798   }
3799
3800 #if ZIPPY
3801         /* Send move to chess program (BEFORE animating it). */
3802         if (appData.zippyPlay && !newGame && newMove && 
3803            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3804
3805             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3806                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3807                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3808                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3809                             move_str);
3810                     DisplayError(str, 0);
3811                 } else {
3812                     if (first.sendTime) {
3813                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3814                     }
3815                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3816                     if (firstMove && !bookHit) {
3817                         firstMove = FALSE;
3818                         if (first.useColors) {
3819                           SendToProgram(gameMode == IcsPlayingWhite ?
3820                                         "white\ngo\n" :
3821                                         "black\ngo\n", &first);
3822                         } else {
3823                           SendToProgram("go\n", &first);
3824                         }
3825                         first.maybeThinking = TRUE;
3826                     }
3827                 }
3828             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3829               if (moveList[moveNum - 1][0] == NULLCHAR) {
3830                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3831                 DisplayError(str, 0);
3832               } else {
3833                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3834                 SendMoveToProgram(moveNum - 1, &first);
3835               }
3836             }
3837         }
3838 #endif
3839     }
3840
3841     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3842         /* If move comes from a remote source, animate it.  If it
3843            isn't remote, it will have already been animated. */
3844         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3845             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3846         }
3847         if (!pausing && appData.highlightLastMove) {
3848             SetHighlights(fromX, fromY, toX, toY);
3849         }
3850     }
3851     
3852     /* Start the clocks */
3853     whiteFlag = blackFlag = FALSE;
3854     appData.clockMode = !(basetime == 0 && increment == 0);
3855     if (ticking == 0) {
3856       ics_clock_paused = TRUE;
3857       StopClocks();
3858     } else if (ticking == 1) {
3859       ics_clock_paused = FALSE;
3860     }
3861     if (gameMode == IcsIdle ||
3862         relation == RELATION_OBSERVING_STATIC ||
3863         relation == RELATION_EXAMINING ||
3864         ics_clock_paused)
3865       DisplayBothClocks();
3866     else
3867       StartClocks();
3868     
3869     /* Display opponents and material strengths */
3870     if (gameInfo.variant != VariantBughouse &&
3871         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3872         if (tinyLayout || smallLayout) {
3873             if(gameInfo.variant == VariantNormal)
3874                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3875                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3876                     basetime, increment);
3877             else
3878                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3879                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3880                     basetime, increment, (int) gameInfo.variant);
3881         } else {
3882             if(gameInfo.variant == VariantNormal)
3883                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3885                     basetime, increment);
3886             else
3887                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3888                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3889                     basetime, increment, VariantName(gameInfo.variant));
3890         }
3891         DisplayTitle(str);
3892   if (appData.debugMode) {
3893     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3894   }
3895     }
3896
3897    
3898     /* Display the board */
3899     if (!pausing && !appData.noGUI) {
3900       
3901       if (appData.premove)
3902           if (!gotPremove || 
3903              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3904              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3905               ClearPremoveHighlights();
3906
3907       DrawPosition(FALSE, boards[currentMove]);
3908       DisplayMove(moveNum - 1);
3909       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3910             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3911               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3912         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3913       }
3914     }
3915
3916     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3917 #if ZIPPY
3918     if(bookHit) { // [HGM] book: simulate book reply
3919         static char bookMove[MSG_SIZ]; // a bit generous?
3920
3921         programStats.nodes = programStats.depth = programStats.time = 
3922         programStats.score = programStats.got_only_move = 0;
3923         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3924
3925         strcpy(bookMove, "move ");
3926         strcat(bookMove, bookHit);
3927         HandleMachineMove(bookMove, &first);
3928     }
3929 #endif
3930 }
3931
3932 void
3933 GetMoveListEvent()
3934 {
3935     char buf[MSG_SIZ];
3936     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3937         ics_getting_history = H_REQUESTED;
3938         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3939         SendToICS(buf);
3940     }
3941 }
3942
3943 void
3944 AnalysisPeriodicEvent(force)
3945      int force;
3946 {
3947     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3948          && !force) || !appData.periodicUpdates)
3949       return;
3950
3951     /* Send . command to Crafty to collect stats */
3952     SendToProgram(".\n", &first);
3953
3954     /* Don't send another until we get a response (this makes
3955        us stop sending to old Crafty's which don't understand
3956        the "." command (sending illegal cmds resets node count & time,
3957        which looks bad)) */
3958     programStats.ok_to_send = 0;
3959 }
3960
3961 void ics_update_width(new_width)
3962         int new_width;
3963 {
3964         ics_printf("set width %d\n", new_width);
3965 }
3966
3967 void
3968 SendMoveToProgram(moveNum, cps)
3969      int moveNum;
3970      ChessProgramState *cps;
3971 {
3972     char buf[MSG_SIZ];
3973
3974     if (cps->useUsermove) {
3975       SendToProgram("usermove ", cps);
3976     }
3977     if (cps->useSAN) {
3978       char *space;
3979       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3980         int len = space - parseList[moveNum];
3981         memcpy(buf, parseList[moveNum], len);
3982         buf[len++] = '\n';
3983         buf[len] = NULLCHAR;
3984       } else {
3985         sprintf(buf, "%s\n", parseList[moveNum]);
3986       }
3987       SendToProgram(buf, cps);
3988     } else {
3989       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3990         AlphaRank(moveList[moveNum], 4);
3991         SendToProgram(moveList[moveNum], cps);
3992         AlphaRank(moveList[moveNum], 4); // and back
3993       } else
3994       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3995        * the engine. It would be nice to have a better way to identify castle 
3996        * moves here. */
3997       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3998                                                                          && cps->useOOCastle) {
3999         int fromX = moveList[moveNum][0] - AAA; 
4000         int fromY = moveList[moveNum][1] - ONE;
4001         int toX = moveList[moveNum][2] - AAA; 
4002         int toY = moveList[moveNum][3] - ONE;
4003         if((boards[moveNum][fromY][fromX] == WhiteKing 
4004             && boards[moveNum][toY][toX] == WhiteRook)
4005            || (boards[moveNum][fromY][fromX] == BlackKing 
4006                && boards[moveNum][toY][toX] == BlackRook)) {
4007           if(toX > fromX) SendToProgram("O-O\n", cps);
4008           else SendToProgram("O-O-O\n", cps);
4009         }
4010         else SendToProgram(moveList[moveNum], cps);
4011       }
4012       else SendToProgram(moveList[moveNum], cps);
4013       /* End of additions by Tord */
4014     }
4015
4016     /* [HGM] setting up the opening has brought engine in force mode! */
4017     /*       Send 'go' if we are in a mode where machine should play. */
4018     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4019         (gameMode == TwoMachinesPlay   ||
4020 #ifdef ZIPPY
4021          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4022 #endif
4023          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4024         SendToProgram("go\n", cps);
4025   if (appData.debugMode) {
4026     fprintf(debugFP, "(extra)\n");
4027   }
4028     }
4029     setboardSpoiledMachineBlack = 0;
4030 }
4031
4032 void
4033 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4034      ChessMove moveType;
4035      int fromX, fromY, toX, toY;
4036 {
4037     char user_move[MSG_SIZ];
4038
4039     switch (moveType) {
4040       default:
4041         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4042                 (int)moveType, fromX, fromY, toX, toY);
4043         DisplayError(user_move + strlen("say "), 0);
4044         break;
4045       case WhiteKingSideCastle:
4046       case BlackKingSideCastle:
4047       case WhiteQueenSideCastleWild:
4048       case BlackQueenSideCastleWild:
4049       /* PUSH Fabien */
4050       case WhiteHSideCastleFR:
4051       case BlackHSideCastleFR:
4052       /* POP Fabien */
4053         sprintf(user_move, "o-o\n");
4054         break;
4055       case WhiteQueenSideCastle:
4056       case BlackQueenSideCastle:
4057       case WhiteKingSideCastleWild:
4058       case BlackKingSideCastleWild:
4059       /* PUSH Fabien */
4060       case WhiteASideCastleFR:
4061       case BlackASideCastleFR:
4062       /* POP Fabien */
4063         sprintf(user_move, "o-o-o\n");
4064         break;
4065       case WhitePromotionQueen:
4066       case BlackPromotionQueen:
4067       case WhitePromotionRook:
4068       case BlackPromotionRook:
4069       case WhitePromotionBishop:
4070       case BlackPromotionBishop:
4071       case WhitePromotionKnight:
4072       case BlackPromotionKnight:
4073       case WhitePromotionKing:
4074       case BlackPromotionKing:
4075       case WhitePromotionChancellor:
4076       case BlackPromotionChancellor:
4077       case WhitePromotionArchbishop:
4078       case BlackPromotionArchbishop:
4079         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4080             sprintf(user_move, "%c%c%c%c=%c\n",
4081                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4082                 PieceToChar(WhiteFerz));
4083         else if(gameInfo.variant == VariantGreat)
4084             sprintf(user_move, "%c%c%c%c=%c\n",
4085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4086                 PieceToChar(WhiteMan));
4087         else
4088             sprintf(user_move, "%c%c%c%c=%c\n",
4089                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4090                 PieceToChar(PromoPiece(moveType)));
4091         break;
4092       case WhiteDrop:
4093       case BlackDrop:
4094         sprintf(user_move, "%c@%c%c\n",
4095                 ToUpper(PieceToChar((ChessSquare) fromX)),
4096                 AAA + toX, ONE + toY);
4097         break;
4098       case NormalMove:
4099       case WhiteCapturesEnPassant:
4100       case BlackCapturesEnPassant:
4101       case IllegalMove:  /* could be a variant we don't quite understand */
4102         sprintf(user_move, "%c%c%c%c\n",
4103                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4104         break;
4105     }
4106     SendToICS(user_move);
4107     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4108         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4109 }
4110
4111 void
4112 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4113      int rf, ff, rt, ft;
4114      char promoChar;
4115      char move[7];
4116 {
4117     if (rf == DROP_RANK) {
4118         sprintf(move, "%c@%c%c\n",
4119                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4120     } else {
4121         if (promoChar == 'x' || promoChar == NULLCHAR) {
4122             sprintf(move, "%c%c%c%c\n",
4123                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4124         } else {
4125             sprintf(move, "%c%c%c%c%c\n",
4126                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4127         }
4128     }
4129 }
4130
4131 void
4132 ProcessICSInitScript(f)
4133      FILE *f;
4134 {
4135     char buf[MSG_SIZ];
4136
4137     while (fgets(buf, MSG_SIZ, f)) {
4138         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4139     }
4140
4141     fclose(f);
4142 }
4143
4144
4145 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4146 void
4147 AlphaRank(char *move, int n)
4148 {
4149 //    char *p = move, c; int x, y;
4150
4151     if (appData.debugMode) {
4152         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4153     }
4154
4155     if(move[1]=='*' && 
4156        move[2]>='0' && move[2]<='9' &&
4157        move[3]>='a' && move[3]<='x'    ) {
4158         move[1] = '@';
4159         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4160         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4161     } else
4162     if(move[0]>='0' && move[0]<='9' &&
4163        move[1]>='a' && move[1]<='x' &&
4164        move[2]>='0' && move[2]<='9' &&
4165        move[3]>='a' && move[3]<='x'    ) {
4166         /* input move, Shogi -> normal */
4167         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4168         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4169         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4170         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4171     } else
4172     if(move[1]=='@' &&
4173        move[3]>='0' && move[3]<='9' &&
4174        move[2]>='a' && move[2]<='x'    ) {
4175         move[1] = '*';
4176         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4177         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4178     } else
4179     if(
4180        move[0]>='a' && move[0]<='x' &&
4181        move[3]>='0' && move[3]<='9' &&
4182        move[2]>='a' && move[2]<='x'    ) {
4183          /* output move, normal -> Shogi */
4184         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4185         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4186         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4187         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4188         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4189     }
4190     if (appData.debugMode) {
4191         fprintf(debugFP, "   out = '%s'\n", move);
4192     }
4193 }
4194
4195 /* Parser for moves from gnuchess, ICS, or user typein box */
4196 Boolean
4197 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4198      char *move;
4199      int moveNum;
4200      ChessMove *moveType;
4201      int *fromX, *fromY, *toX, *toY;
4202      char *promoChar;
4203 {       
4204     if (appData.debugMode) {
4205         fprintf(debugFP, "move to parse: %s\n", move);
4206     }
4207     *moveType = yylexstr(moveNum, move);
4208
4209     switch (*moveType) {
4210       case WhitePromotionChancellor:
4211       case BlackPromotionChancellor:
4212       case WhitePromotionArchbishop:
4213       case BlackPromotionArchbishop:
4214       case WhitePromotionQueen:
4215       case BlackPromotionQueen:
4216       case WhitePromotionRook:
4217       case BlackPromotionRook:
4218       case WhitePromotionBishop:
4219       case BlackPromotionBishop:
4220       case WhitePromotionKnight:
4221       case BlackPromotionKnight:
4222       case WhitePromotionKing:
4223       case BlackPromotionKing:
4224       case NormalMove:
4225       case WhiteCapturesEnPassant:
4226       case BlackCapturesEnPassant:
4227       case WhiteKingSideCastle:
4228       case WhiteQueenSideCastle:
4229       case BlackKingSideCastle:
4230       case BlackQueenSideCastle:
4231       case WhiteKingSideCastleWild:
4232       case WhiteQueenSideCastleWild:
4233       case BlackKingSideCastleWild:
4234       case BlackQueenSideCastleWild:
4235       /* Code added by Tord: */
4236       case WhiteHSideCastleFR:
4237       case WhiteASideCastleFR:
4238       case BlackHSideCastleFR:
4239       case BlackASideCastleFR:
4240       /* End of code added by Tord */
4241       case IllegalMove:         /* bug or odd chess variant */
4242         *fromX = currentMoveString[0] - AAA;
4243         *fromY = currentMoveString[1] - ONE;
4244         *toX = currentMoveString[2] - AAA;
4245         *toY = currentMoveString[3] - ONE;
4246         *promoChar = currentMoveString[4];
4247         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4248             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4249     if (appData.debugMode) {
4250         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4251     }
4252             *fromX = *fromY = *toX = *toY = 0;
4253             return FALSE;
4254         }
4255         if (appData.testLegality) {
4256           return (*moveType != IllegalMove);
4257         } else {
4258           return !(fromX == fromY && toX == toY);
4259         }
4260
4261       case WhiteDrop:
4262       case BlackDrop:
4263         *fromX = *moveType == WhiteDrop ?
4264           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4265           (int) CharToPiece(ToLower(currentMoveString[0]));
4266         *fromY = DROP_RANK;
4267         *toX = currentMoveString[2] - AAA;
4268         *toY = currentMoveString[3] - ONE;
4269         *promoChar = NULLCHAR;
4270         return TRUE;
4271
4272       case AmbiguousMove:
4273       case ImpossibleMove:
4274       case (ChessMove) 0:       /* end of file */
4275       case ElapsedTime:
4276       case Comment:
4277       case PGNTag:
4278       case NAG:
4279       case WhiteWins:
4280       case BlackWins:
4281       case GameIsDrawn:
4282       default:
4283     if (appData.debugMode) {
4284         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4285     }
4286         /* bug? */
4287         *fromX = *fromY = *toX = *toY = 0;
4288         *promoChar = NULLCHAR;
4289         return FALSE;
4290     }
4291 }
4292
4293 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4294 // All positions will have equal probability, but the current method will not provide a unique
4295 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4296 #define DARK 1
4297 #define LITE 2
4298 #define ANY 3
4299
4300 int squaresLeft[4];
4301 int piecesLeft[(int)BlackPawn];
4302 int seed, nrOfShuffles;
4303
4304 void GetPositionNumber()
4305 {       // sets global variable seed
4306         int i;
4307
4308         seed = appData.defaultFrcPosition;
4309         if(seed < 0) { // randomize based on time for negative FRC position numbers
4310                 for(i=0; i<50; i++) seed += random();
4311                 seed = random() ^ random() >> 8 ^ random() << 8;
4312                 if(seed<0) seed = -seed;
4313         }
4314 }
4315
4316 int put(Board board, int pieceType, int rank, int n, int shade)
4317 // put the piece on the (n-1)-th empty squares of the given shade
4318 {
4319         int i;
4320
4321         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4322                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4323                         board[rank][i] = (ChessSquare) pieceType;
4324                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4325                         squaresLeft[ANY]--;
4326                         piecesLeft[pieceType]--; 
4327                         return i;
4328                 }
4329         }
4330         return -1;
4331 }
4332
4333
4334 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4335 // calculate where the next piece goes, (any empty square), and put it there
4336 {
4337         int i;
4338
4339         i = seed % squaresLeft[shade];
4340         nrOfShuffles *= squaresLeft[shade];
4341         seed /= squaresLeft[shade];
4342         put(board, pieceType, rank, i, shade);
4343 }
4344
4345 void AddTwoPieces(Board board, int pieceType, int rank)
4346 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4347 {
4348         int i, n=squaresLeft[ANY], j=n-1, k;
4349
4350         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4351         i = seed % k;  // pick one
4352         nrOfShuffles *= k;
4353         seed /= k;
4354         while(i >= j) i -= j--;
4355         j = n - 1 - j; i += j;
4356         put(board, pieceType, rank, j, ANY);
4357         put(board, pieceType, rank, i, ANY);
4358 }
4359
4360 void SetUpShuffle(Board board, int number)
4361 {
4362         int i, p, first=1;
4363
4364         GetPositionNumber(); nrOfShuffles = 1;
4365
4366         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4367         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4368         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4369
4370         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4371
4372         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4373             p = (int) board[0][i];
4374             if(p < (int) BlackPawn) piecesLeft[p] ++;
4375             board[0][i] = EmptySquare;
4376         }
4377
4378         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4379             // shuffles restricted to allow normal castling put KRR first
4380             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4381                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4382             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4383                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4384             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4385                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4386             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4387                 put(board, WhiteRook, 0, 0, ANY);
4388             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4389         }
4390
4391         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4392             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4393             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4394                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4395                 while(piecesLeft[p] >= 2) {
4396                     AddOnePiece(board, p, 0, LITE);
4397                     AddOnePiece(board, p, 0, DARK);
4398                 }
4399                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4400             }
4401
4402         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4403             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4404             // but we leave King and Rooks for last, to possibly obey FRC restriction
4405             if(p == (int)WhiteRook) continue;
4406             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4407             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4408         }
4409
4410         // now everything is placed, except perhaps King (Unicorn) and Rooks
4411
4412         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4413             // Last King gets castling rights
4414             while(piecesLeft[(int)WhiteUnicorn]) {
4415                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4416                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4417             }
4418
4419             while(piecesLeft[(int)WhiteKing]) {
4420                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4421                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4422             }
4423
4424
4425         } else {
4426             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4427             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4428         }
4429
4430         // Only Rooks can be left; simply place them all
4431         while(piecesLeft[(int)WhiteRook]) {
4432                 i = put(board, WhiteRook, 0, 0, ANY);
4433                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4434                         if(first) {
4435                                 first=0;
4436                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4437                         }
4438                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4439                 }
4440         }
4441         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4442             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4443         }
4444
4445         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4446 }
4447
4448 int SetCharTable( char *table, const char * map )
4449 /* [HGM] moved here from winboard.c because of its general usefulness */
4450 /*       Basically a safe strcpy that uses the last character as King */
4451 {
4452     int result = FALSE; int NrPieces;
4453
4454     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4455                     && NrPieces >= 12 && !(NrPieces&1)) {
4456         int i; /* [HGM] Accept even length from 12 to 34 */
4457
4458         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4459         for( i=0; i<NrPieces/2-1; i++ ) {
4460             table[i] = map[i];
4461             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4462         }
4463         table[(int) WhiteKing]  = map[NrPieces/2-1];
4464         table[(int) BlackKing]  = map[NrPieces-1];
4465
4466         result = TRUE;
4467     }
4468
4469     return result;
4470 }
4471
4472 void Prelude(Board board)
4473 {       // [HGM] superchess: random selection of exo-pieces
4474         int i, j, k; ChessSquare p; 
4475         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4476
4477         GetPositionNumber(); // use FRC position number
4478
4479         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4480             SetCharTable(pieceToChar, appData.pieceToCharTable);
4481             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4482                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4483         }
4484
4485         j = seed%4;                 seed /= 4; 
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 >= j); seed /= 3; 
4490         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3; 
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%2 + (seed%2 >= j); seed /= 2; 
4498         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4499         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4500         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4501         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4502         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4503         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4504         put(board, exoPieces[0],    0, 0, ANY);
4505         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4506 }
4507
4508 void
4509 InitPosition(redraw)
4510      int redraw;
4511 {
4512     ChessSquare (* pieces)[BOARD_SIZE];
4513     int i, j, pawnRow, overrule,
4514     oldx = gameInfo.boardWidth,
4515     oldy = gameInfo.boardHeight,
4516     oldh = gameInfo.holdingsWidth,
4517     oldv = gameInfo.variant;
4518
4519     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4520
4521     /* [AS] Initialize pv info list [HGM] and game status */
4522     {
4523         for( i=0; i<MAX_MOVES; i++ ) {
4524             pvInfoList[i].depth = 0;
4525             epStatus[i]=EP_NONE;
4526             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4527         }
4528
4529         initialRulePlies = 0; /* 50-move counter start */
4530
4531         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4532         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4533     }
4534
4535     
4536     /* [HGM] logic here is completely changed. In stead of full positions */
4537     /* the initialized data only consist of the two backranks. The switch */
4538     /* selects which one we will use, which is than copied to the Board   */
4539     /* initialPosition, which for the rest is initialized by Pawns and    */
4540     /* empty squares. This initial position is then copied to boards[0],  */
4541     /* possibly after shuffling, so that it remains available.            */
4542
4543     gameInfo.holdingsWidth = 0; /* default board sizes */
4544     gameInfo.boardWidth    = 8;
4545     gameInfo.boardHeight   = 8;
4546     gameInfo.holdingsSize  = 0;
4547     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4548     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4549     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4550
4551     switch (gameInfo.variant) {
4552     case VariantFischeRandom:
4553       shuffleOpenings = TRUE;
4554     default:
4555       pieces = FIDEArray;
4556       break;
4557     case VariantShatranj:
4558       pieces = ShatranjArray;
4559       nrCastlingRights = 0;
4560       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4561       break;
4562     case VariantTwoKings:
4563       pieces = twoKingsArray;
4564       break;
4565     case VariantCapaRandom:
4566       shuffleOpenings = TRUE;
4567     case VariantCapablanca:
4568       pieces = CapablancaArray;
4569       gameInfo.boardWidth = 10;
4570       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4571       break;
4572     case VariantGothic:
4573       pieces = GothicArray;
4574       gameInfo.boardWidth = 10;
4575       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4576       break;
4577     case VariantJanus:
4578       pieces = JanusArray;
4579       gameInfo.boardWidth = 10;
4580       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4581       nrCastlingRights = 6;
4582         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4583         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4584         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4585         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4586         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4587         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4588       break;
4589     case VariantFalcon:
4590       pieces = FalconArray;
4591       gameInfo.boardWidth = 10;
4592       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4593       break;
4594     case VariantXiangqi:
4595       pieces = XiangqiArray;
4596       gameInfo.boardWidth  = 9;
4597       gameInfo.boardHeight = 10;
4598       nrCastlingRights = 0;
4599       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4600       break;
4601     case VariantShogi:
4602       pieces = ShogiArray;
4603       gameInfo.boardWidth  = 9;
4604       gameInfo.boardHeight = 9;
4605       gameInfo.holdingsSize = 7;
4606       nrCastlingRights = 0;
4607       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4608       break;
4609     case VariantCourier:
4610       pieces = CourierArray;
4611       gameInfo.boardWidth  = 12;
4612       nrCastlingRights = 0;
4613       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4614       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4615       break;
4616     case VariantKnightmate:
4617       pieces = KnightmateArray;
4618       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4619       break;
4620     case VariantFairy:
4621       pieces = fairyArray;
4622       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4623       break;
4624     case VariantGreat:
4625       pieces = GreatArray;
4626       gameInfo.boardWidth = 10;
4627       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4628       gameInfo.holdingsSize = 8;
4629       break;
4630     case VariantSuper:
4631       pieces = FIDEArray;
4632       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4633       gameInfo.holdingsSize = 8;
4634       startedFromSetupPosition = TRUE;
4635       break;
4636     case VariantCrazyhouse:
4637     case VariantBughouse:
4638       pieces = FIDEArray;
4639       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4640       gameInfo.holdingsSize = 5;
4641       break;
4642     case VariantWildCastle:
4643       pieces = FIDEArray;
4644       /* !!?shuffle with kings guaranteed to be on d or e file */
4645       shuffleOpenings = 1;
4646       break;
4647     case VariantNoCastle:
4648       pieces = FIDEArray;
4649       nrCastlingRights = 0;
4650       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4651       /* !!?unconstrained back-rank shuffle */
4652       shuffleOpenings = 1;
4653       break;
4654     }
4655
4656     overrule = 0;
4657     if(appData.NrFiles >= 0) {
4658         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4659         gameInfo.boardWidth = appData.NrFiles;
4660     }
4661     if(appData.NrRanks >= 0) {
4662         gameInfo.boardHeight = appData.NrRanks;
4663     }
4664     if(appData.holdingsSize >= 0) {
4665         i = appData.holdingsSize;
4666         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4667         gameInfo.holdingsSize = i;
4668     }
4669     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4670     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4671         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4672
4673     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4674     if(pawnRow < 1) pawnRow = 1;
4675
4676     /* User pieceToChar list overrules defaults */
4677     if(appData.pieceToCharTable != NULL)
4678         SetCharTable(pieceToChar, appData.pieceToCharTable);
4679
4680     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4681
4682         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4683             s = (ChessSquare) 0; /* account holding counts in guard band */
4684         for( i=0; i<BOARD_HEIGHT; i++ )
4685             initialPosition[i][j] = s;
4686
4687         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4688         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4689         initialPosition[pawnRow][j] = WhitePawn;
4690         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4691         if(gameInfo.variant == VariantXiangqi) {
4692             if(j&1) {
4693                 initialPosition[pawnRow][j] = 
4694                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4695                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4696                    initialPosition[2][j] = WhiteCannon;
4697                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4698                 }
4699             }
4700         }
4701         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4702     }
4703     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4704
4705             j=BOARD_LEFT+1;
4706             initialPosition[1][j] = WhiteBishop;
4707             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4708             j=BOARD_RGHT-2;
4709             initialPosition[1][j] = WhiteRook;
4710             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4711     }
4712
4713     if( nrCastlingRights == -1) {
4714         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4715         /*       This sets default castling rights from none to normal corners   */
4716         /* Variants with other castling rights must set them themselves above    */
4717         nrCastlingRights = 6;
4718        
4719         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4720         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4721         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4722         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4723         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4724         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4725      }
4726
4727      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4728      if(gameInfo.variant == VariantGreat) { // promotion commoners
4729         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4730         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4731         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4732         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4733      }
4734   if (appData.debugMode) {
4735     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4736   }
4737     if(shuffleOpenings) {
4738         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4739         startedFromSetupPosition = TRUE;
4740     }
4741     if(startedFromPositionFile) {
4742       /* [HGM] loadPos: use PositionFile for every new game */
4743       CopyBoard(initialPosition, filePosition);
4744       for(i=0; i<nrCastlingRights; i++)
4745           castlingRights[0][i] = initialRights[i] = fileRights[i];
4746       startedFromSetupPosition = TRUE;
4747     }
4748
4749     CopyBoard(boards[0], initialPosition);
4750
4751     if(oldx != gameInfo.boardWidth ||
4752        oldy != gameInfo.boardHeight ||
4753        oldh != gameInfo.holdingsWidth
4754 #ifdef GOTHIC
4755        || oldv == VariantGothic ||        // For licensing popups
4756        gameInfo.variant == VariantGothic
4757 #endif
4758 #ifdef FALCON
4759        || oldv == VariantFalcon ||
4760        gameInfo.variant == VariantFalcon
4761 #endif
4762                                          )
4763             InitDrawingSizes(-2 ,0);
4764
4765     if (redraw)
4766       DrawPosition(TRUE, boards[currentMove]);
4767 }
4768
4769 void
4770 SendBoard(cps, moveNum)
4771      ChessProgramState *cps;
4772      int moveNum;
4773 {
4774     char message[MSG_SIZ];
4775     
4776     if (cps->useSetboard) {
4777       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4778       sprintf(message, "setboard %s\n", fen);
4779       SendToProgram(message, cps);
4780       free(fen);
4781
4782     } else {
4783       ChessSquare *bp;
4784       int i, j;
4785       /* Kludge to set black to move, avoiding the troublesome and now
4786        * deprecated "black" command.
4787        */
4788       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4789
4790       SendToProgram("edit\n", cps);
4791       SendToProgram("#\n", cps);
4792       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4793         bp = &boards[moveNum][i][BOARD_LEFT];
4794         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4795           if ((int) *bp < (int) BlackPawn) {
4796             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4797                     AAA + j, ONE + i);
4798             if(message[0] == '+' || message[0] == '~') {
4799                 sprintf(message, "%c%c%c+\n",
4800                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4801                         AAA + j, ONE + i);
4802             }
4803             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4804                 message[1] = BOARD_RGHT   - 1 - j + '1';
4805                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4806             }
4807             SendToProgram(message, cps);
4808           }
4809         }
4810       }
4811     
4812       SendToProgram("c\n", cps);
4813       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4814         bp = &boards[moveNum][i][BOARD_LEFT];
4815         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4816           if (((int) *bp != (int) EmptySquare)
4817               && ((int) *bp >= (int) BlackPawn)) {
4818             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4819                     AAA + j, ONE + i);
4820             if(message[0] == '+' || message[0] == '~') {
4821                 sprintf(message, "%c%c%c+\n",
4822                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4823                         AAA + j, ONE + i);
4824             }
4825             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4826                 message[1] = BOARD_RGHT   - 1 - j + '1';
4827                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4828             }
4829             SendToProgram(message, cps);
4830           }
4831         }
4832       }
4833     
4834       SendToProgram(".\n", cps);
4835     }
4836     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4837 }
4838
4839 int
4840 IsPromotion(fromX, fromY, toX, toY)
4841      int fromX, fromY, toX, toY;
4842 {
4843     /* [HGM] add Shogi promotions */
4844     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4845     ChessSquare piece;
4846
4847     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4848       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4849    /* [HGM] Note to self: line above also weeds out drops */
4850     piece = boards[currentMove][fromY][fromX];
4851     if(gameInfo.variant == VariantShogi) {
4852         promotionZoneSize = 3;
4853         highestPromotingPiece = (int)WhiteKing;
4854         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4855            and if in normal chess we then allow promotion to King, why not
4856            allow promotion of other piece in Shogi?                         */
4857     }
4858     if((int)piece >= BlackPawn) {
4859         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4860              return FALSE;
4861         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4862     } else {
4863         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4864            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4865     }
4866     return ( (int)piece <= highestPromotingPiece );
4867 }
4868
4869 int
4870 InPalace(row, column)
4871      int row, column;
4872 {   /* [HGM] for Xiangqi */
4873     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4874          column < (BOARD_WIDTH + 4)/2 &&
4875          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4876     return FALSE;
4877 }
4878
4879 int
4880 PieceForSquare (x, y)
4881      int x;
4882      int y;
4883 {
4884   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4885      return -1;
4886   else
4887      return boards[currentMove][y][x];
4888 }
4889
4890 int
4891 OKToStartUserMove(x, y)
4892      int x, y;
4893 {
4894     ChessSquare from_piece;
4895     int white_piece;
4896
4897     if (matchMode) return FALSE;
4898     if (gameMode == EditPosition) return TRUE;
4899
4900     if (x >= 0 && y >= 0)
4901       from_piece = boards[currentMove][y][x];
4902     else
4903       from_piece = EmptySquare;
4904
4905     if (from_piece == EmptySquare) return FALSE;
4906
4907     white_piece = (int)from_piece >= (int)WhitePawn &&
4908       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4909
4910     switch (gameMode) {
4911       case PlayFromGameFile:
4912       case AnalyzeFile:
4913       case TwoMachinesPlay:
4914       case EndOfGame:
4915         return FALSE;
4916
4917       case IcsObserving:
4918       case IcsIdle:
4919         return FALSE;
4920
4921       case MachinePlaysWhite:
4922       case IcsPlayingBlack:
4923         if (appData.zippyPlay) return FALSE;
4924         if (white_piece) {
4925             DisplayMoveError(_("You are playing Black"));
4926             return FALSE;
4927         }
4928         break;
4929
4930       case MachinePlaysBlack:
4931       case IcsPlayingWhite:
4932         if (appData.zippyPlay) return FALSE;
4933         if (!white_piece) {
4934             DisplayMoveError(_("You are playing White"));
4935             return FALSE;
4936         }
4937         break;
4938
4939       case EditGame:
4940         if (!white_piece && WhiteOnMove(currentMove)) {
4941             DisplayMoveError(_("It is White's turn"));
4942             return FALSE;
4943         }           
4944         if (white_piece && !WhiteOnMove(currentMove)) {
4945             DisplayMoveError(_("It is Black's turn"));
4946             return FALSE;
4947         }           
4948         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4949             /* Editing correspondence game history */
4950             /* Could disallow this or prompt for confirmation */
4951             cmailOldMove = -1;
4952         }
4953         if (currentMove < forwardMostMove) {
4954             /* Discarding moves */
4955             /* Could prompt for confirmation here,
4956                but I don't think that's such a good idea */
4957             forwardMostMove = currentMove;
4958         }
4959         break;
4960
4961       case BeginningOfGame:
4962         if (appData.icsActive) return FALSE;
4963         if (!appData.noChessProgram) {
4964             if (!white_piece) {
4965                 DisplayMoveError(_("You are playing White"));
4966                 return FALSE;
4967             }
4968         }
4969         break;
4970         
4971       case Training:
4972         if (!white_piece && WhiteOnMove(currentMove)) {
4973             DisplayMoveError(_("It is White's turn"));
4974             return FALSE;
4975         }           
4976         if (white_piece && !WhiteOnMove(currentMove)) {
4977             DisplayMoveError(_("It is Black's turn"));
4978             return FALSE;
4979         }           
4980         break;
4981
4982       default:
4983       case IcsExamining:
4984         break;
4985     }
4986     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4987         && gameMode != AnalyzeFile && gameMode != Training) {
4988         DisplayMoveError(_("Displayed position is not current"));
4989         return FALSE;
4990     }
4991     return TRUE;
4992 }
4993
4994 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4995 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4996 int lastLoadGameUseList = FALSE;
4997 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4998 ChessMove lastLoadGameStart = (ChessMove) 0;
4999
5000 ChessMove
5001 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5002      int fromX, fromY, toX, toY;
5003      int promoChar;
5004      Boolean captureOwn;
5005 {
5006     ChessMove moveType;
5007     ChessSquare pdown, pup;
5008
5009     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5010
5011     /* [HGM] suppress all moves into holdings area and guard band */
5012     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5013             return ImpossibleMove;
5014
5015     /* [HGM] <sameColor> moved to here from winboard.c */
5016     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5017     pdown = boards[currentMove][fromY][fromX];
5018     pup = boards[currentMove][toY][toX];
5019     if (    gameMode != EditPosition && !captureOwn &&
5020             (WhitePawn <= pdown && pdown < BlackPawn &&
5021              WhitePawn <= pup && pup < BlackPawn  ||
5022              BlackPawn <= pdown && pdown < EmptySquare &&
5023              BlackPawn <= pup && pup < EmptySquare 
5024             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5025                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5026                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5027                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5028                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5029         )           )
5030          return Comment;
5031
5032     /* Check if the user is playing in turn.  This is complicated because we
5033        let the user "pick up" a piece before it is his turn.  So the piece he
5034        tried to pick up may have been captured by the time he puts it down!
5035        Therefore we use the color the user is supposed to be playing in this
5036        test, not the color of the piece that is currently on the starting
5037        square---except in EditGame mode, where the user is playing both
5038        sides; fortunately there the capture race can't happen.  (It can
5039        now happen in IcsExamining mode, but that's just too bad.  The user
5040        will get a somewhat confusing message in that case.)
5041        */
5042
5043     switch (gameMode) {
5044       case PlayFromGameFile:
5045       case AnalyzeFile:
5046       case TwoMachinesPlay:
5047       case EndOfGame:
5048       case IcsObserving:
5049       case IcsIdle:
5050         /* We switched into a game mode where moves are not accepted,
5051            perhaps while the mouse button was down. */
5052         return ImpossibleMove;
5053
5054       case MachinePlaysWhite:
5055         /* User is moving for Black */
5056         if (WhiteOnMove(currentMove)) {
5057             DisplayMoveError(_("It is White's turn"));
5058             return ImpossibleMove;
5059         }
5060         break;
5061
5062       case MachinePlaysBlack:
5063         /* User is moving for White */
5064         if (!WhiteOnMove(currentMove)) {
5065             DisplayMoveError(_("It is Black's turn"));
5066             return ImpossibleMove;
5067         }
5068         break;
5069
5070       case EditGame:
5071       case IcsExamining:
5072       case BeginningOfGame:
5073       case AnalyzeMode:
5074       case Training:
5075         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5076             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5077             /* User is moving for Black */
5078             if (WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is White's turn"));
5080                 return ImpossibleMove;
5081             }
5082         } else {
5083             /* User is moving for White */
5084             if (!WhiteOnMove(currentMove)) {
5085                 DisplayMoveError(_("It is Black's turn"));
5086                 return ImpossibleMove;
5087             }
5088         }
5089         break;
5090
5091       case IcsPlayingBlack:
5092         /* User is moving for Black */
5093         if (WhiteOnMove(currentMove)) {
5094             if (!appData.premove) {
5095                 DisplayMoveError(_("It is White's turn"));
5096             } else if (toX >= 0 && toY >= 0) {
5097                 premoveToX = toX;
5098                 premoveToY = toY;
5099                 premoveFromX = fromX;
5100                 premoveFromY = fromY;
5101                 premovePromoChar = promoChar;
5102                 gotPremove = 1;
5103                 if (appData.debugMode) 
5104                     fprintf(debugFP, "Got premove: fromX %d,"
5105                             "fromY %d, toX %d, toY %d\n",
5106                             fromX, fromY, toX, toY);
5107             }
5108             return ImpossibleMove;
5109         }
5110         break;
5111
5112       case IcsPlayingWhite:
5113         /* User is moving for White */
5114         if (!WhiteOnMove(currentMove)) {
5115             if (!appData.premove) {
5116                 DisplayMoveError(_("It is Black's turn"));
5117             } else if (toX >= 0 && toY >= 0) {
5118                 premoveToX = toX;
5119                 premoveToY = toY;
5120                 premoveFromX = fromX;
5121                 premoveFromY = fromY;
5122                 premovePromoChar = promoChar;
5123                 gotPremove = 1;
5124                 if (appData.debugMode) 
5125                     fprintf(debugFP, "Got premove: fromX %d,"
5126                             "fromY %d, toX %d, toY %d\n",
5127                             fromX, fromY, toX, toY);
5128             }
5129             return ImpossibleMove;
5130         }
5131         break;
5132
5133       default:
5134         break;
5135
5136       case EditPosition:
5137         /* EditPosition, empty square, or different color piece;
5138            click-click move is possible */
5139         if (toX == -2 || toY == -2) {
5140             boards[0][fromY][fromX] = EmptySquare;
5141             return AmbiguousMove;
5142         } else if (toX >= 0 && toY >= 0) {
5143             boards[0][toY][toX] = boards[0][fromY][fromX];
5144             boards[0][fromY][fromX] = EmptySquare;
5145             return AmbiguousMove;
5146         }
5147         return ImpossibleMove;
5148     }
5149
5150     /* [HGM] If move started in holdings, it means a drop */
5151     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5152          if( pup != EmptySquare ) return ImpossibleMove;
5153          if(appData.testLegality) {
5154              /* it would be more logical if LegalityTest() also figured out
5155               * which drops are legal. For now we forbid pawns on back rank.
5156               * Shogi is on its own here...
5157               */
5158              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5159                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5160                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5161          }
5162          return WhiteDrop; /* Not needed to specify white or black yet */
5163     }
5164
5165     userOfferedDraw = FALSE;
5166         
5167     /* [HGM] always test for legality, to get promotion info */
5168     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5169                           epStatus[currentMove], castlingRights[currentMove],
5170                                          fromY, fromX, toY, toX, promoChar);
5171     /* [HGM] but possibly ignore an IllegalMove result */
5172     if (appData.testLegality) {
5173         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5174             DisplayMoveError(_("Illegal move"));
5175             return ImpossibleMove;
5176         }
5177     }
5178 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5179     return moveType;
5180     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5181        function is made into one that returns an OK move type if FinishMove
5182        should be called. This to give the calling driver routine the
5183        opportunity to finish the userMove input with a promotion popup,
5184        without bothering the user with this for invalid or illegal moves */
5185
5186 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5187 }
5188
5189 /* Common tail of UserMoveEvent and DropMenuEvent */
5190 int
5191 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5192      ChessMove moveType;
5193      int fromX, fromY, toX, toY;
5194      /*char*/int promoChar;
5195 {
5196     char *bookHit = 0;
5197 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5198     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5199         // [HGM] superchess: suppress promotions to non-available piece
5200         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5201         if(WhiteOnMove(currentMove)) {
5202             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5203         } else {
5204             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5205         }
5206     }
5207
5208     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5209        move type in caller when we know the move is a legal promotion */
5210     if(moveType == NormalMove && promoChar)
5211         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5212 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5213     /* [HGM] convert drag-and-drop piece drops to standard form */
5214     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5215          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5216            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5217                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5218 //         fromX = boards[currentMove][fromY][fromX];
5219            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5220            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5221            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5222            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5223          fromY = DROP_RANK;
5224     }
5225
5226     /* [HGM] <popupFix> The following if has been moved here from
5227        UserMoveEvent(). Because it seemed to belon here (why not allow
5228        piece drops in training games?), and because it can only be
5229        performed after it is known to what we promote. */
5230     if (gameMode == Training) {
5231       /* compare the move played on the board to the next move in the
5232        * game. If they match, display the move and the opponent's response. 
5233        * If they don't match, display an error message.
5234        */
5235       int saveAnimate;
5236       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5237       CopyBoard(testBoard, boards[currentMove]);
5238       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5239
5240       if (CompareBoards(testBoard, boards[currentMove+1])) {
5241         ForwardInner(currentMove+1);
5242
5243         /* Autoplay the opponent's response.
5244          * if appData.animate was TRUE when Training mode was entered,
5245          * the response will be animated.
5246          */
5247         saveAnimate = appData.animate;
5248         appData.animate = animateTraining;
5249         ForwardInner(currentMove+1);
5250         appData.animate = saveAnimate;
5251
5252         /* check for the end of the game */
5253         if (currentMove >= forwardMostMove) {
5254           gameMode = PlayFromGameFile;
5255           ModeHighlight();
5256           SetTrainingModeOff();
5257           DisplayInformation(_("End of game"));
5258         }
5259       } else {
5260         DisplayError(_("Incorrect move"), 0);
5261       }
5262       return 1;
5263     }
5264
5265   /* Ok, now we know that the move is good, so we can kill
5266      the previous line in Analysis Mode */
5267   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5268     forwardMostMove = currentMove;
5269   }
5270
5271   /* If we need the chess program but it's dead, restart it */
5272   ResurrectChessProgram();
5273
5274   /* A user move restarts a paused game*/
5275   if (pausing)
5276     PauseEvent();
5277
5278   thinkOutput[0] = NULLCHAR;
5279
5280   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5281
5282   if (gameMode == BeginningOfGame) {
5283     if (appData.noChessProgram) {
5284       gameMode = EditGame;
5285       SetGameInfo();
5286     } else {
5287       char buf[MSG_SIZ];
5288       gameMode = MachinePlaysBlack;
5289       StartClocks();
5290       SetGameInfo();
5291       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5292       DisplayTitle(buf);
5293       if (first.sendName) {
5294         sprintf(buf, "name %s\n", gameInfo.white);
5295         SendToProgram(buf, &first);
5296       }
5297       StartClocks();
5298     }
5299     ModeHighlight();
5300   }
5301 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5302   /* Relay move to ICS or chess engine */
5303   if (appData.icsActive) {
5304     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5305         gameMode == IcsExamining) {
5306       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5307       ics_user_moved = 1;
5308     }
5309   } else {
5310     if (first.sendTime && (gameMode == BeginningOfGame ||
5311                            gameMode == MachinePlaysWhite ||
5312                            gameMode == MachinePlaysBlack)) {
5313       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5314     }
5315     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5316          // [HGM] book: if program might be playing, let it use book
5317         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5318         first.maybeThinking = TRUE;
5319     } else SendMoveToProgram(forwardMostMove-1, &first);
5320     if (currentMove == cmailOldMove + 1) {
5321       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5322     }
5323   }
5324
5325   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5326
5327   switch (gameMode) {
5328   case EditGame:
5329     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5330                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5331     case MT_NONE:
5332     case MT_CHECK:
5333       break;
5334     case MT_CHECKMATE:
5335     case MT_STAINMATE:
5336       if (WhiteOnMove(currentMove)) {
5337         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5338       } else {
5339         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5340       }
5341       break;
5342     case MT_STALEMATE:
5343       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5344       break;
5345     }
5346     break;
5347     
5348   case MachinePlaysBlack:
5349   case MachinePlaysWhite:
5350     /* disable certain menu options while machine is thinking */
5351     SetMachineThinkingEnables();
5352     break;
5353
5354   default:
5355     break;
5356   }
5357
5358   if(bookHit) { // [HGM] book: simulate book reply
5359         static char bookMove[MSG_SIZ]; // a bit generous?
5360
5361         programStats.nodes = programStats.depth = programStats.time = 
5362         programStats.score = programStats.got_only_move = 0;
5363         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5364
5365         strcpy(bookMove, "move ");
5366         strcat(bookMove, bookHit);
5367         HandleMachineMove(bookMove, &first);
5368   }
5369   return 1;
5370 }
5371
5372 void
5373 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5374      int fromX, fromY, toX, toY;
5375      int promoChar;
5376 {
5377     /* [HGM] This routine was added to allow calling of its two logical
5378        parts from other modules in the old way. Before, UserMoveEvent()
5379        automatically called FinishMove() if the move was OK, and returned
5380        otherwise. I separated the two, in order to make it possible to
5381        slip a promotion popup in between. But that it always needs two
5382        calls, to the first part, (now called UserMoveTest() ), and to
5383        FinishMove if the first part succeeded. Calls that do not need
5384        to do anything in between, can call this routine the old way. 
5385     */
5386     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5387 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5388     if(moveType == AmbiguousMove)
5389         DrawPosition(FALSE, boards[currentMove]);
5390     else if(moveType != ImpossibleMove && moveType != Comment)
5391         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5392 }
5393
5394 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5395 {
5396 //    char * hint = lastHint;
5397     FrontEndProgramStats stats;
5398
5399     stats.which = cps == &first ? 0 : 1;
5400     stats.depth = cpstats->depth;
5401     stats.nodes = cpstats->nodes;
5402     stats.score = cpstats->score;
5403     stats.time = cpstats->time;
5404     stats.pv = cpstats->movelist;
5405     stats.hint = lastHint;
5406     stats.an_move_index = 0;
5407     stats.an_move_count = 0;
5408
5409     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5410         stats.hint = cpstats->move_name;
5411         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5412         stats.an_move_count = cpstats->nr_moves;
5413     }
5414
5415     SetProgramStats( &stats );
5416 }
5417
5418 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5419 {   // [HGM] book: this routine intercepts moves to simulate book replies
5420     char *bookHit = NULL;
5421
5422     //first determine if the incoming move brings opponent into his book
5423     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5424         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5425     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5426     if(bookHit != NULL && !cps->bookSuspend) {
5427         // make sure opponent is not going to reply after receiving move to book position
5428         SendToProgram("force\n", cps);
5429         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5430     }
5431     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5432     // now arrange restart after book miss
5433     if(bookHit) {
5434         // after a book hit we never send 'go', and the code after the call to this routine
5435         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5436         char buf[MSG_SIZ];
5437         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5438         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5439         SendToProgram(buf, cps);
5440         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5441     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5442         SendToProgram("go\n", cps);
5443         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5444     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5445         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5446             SendToProgram("go\n", cps); 
5447         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5448     }
5449     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5450 }
5451
5452 char *savedMessage;
5453 ChessProgramState *savedState;
5454 void DeferredBookMove(void)
5455 {
5456         if(savedState->lastPing != savedState->lastPong)
5457                     ScheduleDelayedEvent(DeferredBookMove, 10);
5458         else
5459         HandleMachineMove(savedMessage, savedState);
5460 }
5461
5462 void
5463 HandleMachineMove(message, cps)
5464      char *message;
5465      ChessProgramState *cps;
5466 {
5467     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5468     char realname[MSG_SIZ];
5469     int fromX, fromY, toX, toY;
5470     ChessMove moveType;
5471     char promoChar;
5472     char *p;
5473     int machineWhite;
5474     char *bookHit;
5475
5476 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5477     /*
5478      * Kludge to ignore BEL characters
5479      */
5480     while (*message == '\007') message++;
5481
5482     /*
5483      * [HGM] engine debug message: ignore lines starting with '#' character
5484      */
5485     if(cps->debug && *message == '#') return;
5486
5487     /*
5488      * Look for book output
5489      */
5490     if (cps == &first && bookRequested) {
5491         if (message[0] == '\t' || message[0] == ' ') {
5492             /* Part of the book output is here; append it */
5493             strcat(bookOutput, message);
5494             strcat(bookOutput, "  \n");
5495             return;
5496         } else if (bookOutput[0] != NULLCHAR) {
5497             /* All of book output has arrived; display it */
5498             char *p = bookOutput;
5499             while (*p != NULLCHAR) {
5500                 if (*p == '\t') *p = ' ';
5501                 p++;
5502             }
5503             DisplayInformation(bookOutput);
5504             bookRequested = FALSE;
5505             /* Fall through to parse the current output */
5506         }
5507     }
5508
5509     /*
5510      * Look for machine move.
5511      */
5512     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5513         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5514     {
5515         /* This method is only useful on engines that support ping */
5516         if (cps->lastPing != cps->lastPong) {
5517           if (gameMode == BeginningOfGame) {
5518             /* Extra move from before last new; ignore */
5519             if (appData.debugMode) {
5520                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5521             }
5522           } else {
5523             if (appData.debugMode) {
5524                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5525                         cps->which, gameMode);
5526             }
5527
5528             SendToProgram("undo\n", cps);
5529           }
5530           return;
5531         }
5532
5533         switch (gameMode) {
5534           case BeginningOfGame:
5535             /* Extra move from before last reset; ignore */
5536             if (appData.debugMode) {
5537                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5538             }
5539             return;
5540
5541           case EndOfGame:
5542           case IcsIdle:
5543           default:
5544             /* Extra move after we tried to stop.  The mode test is
5545                not a reliable way of detecting this problem, but it's
5546                the best we can do on engines that don't support ping.
5547             */
5548             if (appData.debugMode) {
5549                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5550                         cps->which, gameMode);
5551             }
5552             SendToProgram("undo\n", cps);
5553             return;
5554
5555           case MachinePlaysWhite:
5556           case IcsPlayingWhite:
5557             machineWhite = TRUE;
5558             break;
5559
5560           case MachinePlaysBlack:
5561           case IcsPlayingBlack:
5562             machineWhite = FALSE;
5563             break;
5564
5565           case TwoMachinesPlay:
5566             machineWhite = (cps->twoMachinesColor[0] == 'w');
5567             break;
5568         }
5569         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5570             if (appData.debugMode) {
5571                 fprintf(debugFP,
5572                         "Ignoring move out of turn by %s, gameMode %d"
5573                         ", forwardMost %d\n",
5574                         cps->which, gameMode, forwardMostMove);
5575             }
5576             return;
5577         }
5578
5579     if (appData.debugMode) { int f = forwardMostMove;
5580         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5581                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5582     }
5583         if(cps->alphaRank) AlphaRank(machineMove, 4);
5584         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5585                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5586             /* Machine move could not be parsed; ignore it. */
5587             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5588                     machineMove, cps->which);
5589             DisplayError(buf1, 0);
5590             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5591                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5592             if (gameMode == TwoMachinesPlay) {
5593               GameEnds(machineWhite ? BlackWins : WhiteWins,
5594                        buf1, GE_XBOARD);
5595             }
5596             return;
5597         }
5598
5599         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5600         /* So we have to redo legality test with true e.p. status here,  */
5601         /* to make sure an illegal e.p. capture does not slip through,   */
5602         /* to cause a forfeit on a justified illegal-move complaint      */
5603         /* of the opponent.                                              */
5604         if( gameMode==TwoMachinesPlay && appData.testLegality
5605             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5606                                                               ) {
5607            ChessMove moveType;
5608            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5609                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5610                              fromY, fromX, toY, toX, promoChar);
5611             if (appData.debugMode) {
5612                 int i;
5613                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5614                     castlingRights[forwardMostMove][i], castlingRank[i]);
5615                 fprintf(debugFP, "castling rights\n");
5616             }
5617             if(moveType == IllegalMove) {
5618                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5619                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5620                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5621                            buf1, GE_XBOARD);
5622                 return;
5623            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5624            /* [HGM] Kludge to handle engines that send FRC-style castling
5625               when they shouldn't (like TSCP-Gothic) */
5626            switch(moveType) {
5627              case WhiteASideCastleFR:
5628              case BlackASideCastleFR:
5629                toX+=2;
5630                currentMoveString[2]++;
5631                break;
5632              case WhiteHSideCastleFR:
5633              case BlackHSideCastleFR:
5634                toX--;
5635                currentMoveString[2]--;
5636                break;
5637              default: ; // nothing to do, but suppresses warning of pedantic compilers
5638            }
5639         }
5640         hintRequested = FALSE;
5641         lastHint[0] = NULLCHAR;
5642         bookRequested = FALSE;
5643         /* Program may be pondering now */
5644         cps->maybeThinking = TRUE;
5645         if (cps->sendTime == 2) cps->sendTime = 1;
5646         if (cps->offeredDraw) cps->offeredDraw--;
5647
5648 #if ZIPPY
5649         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5650             first.initDone) {
5651           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5652           ics_user_moved = 1;
5653           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5654                 char buf[3*MSG_SIZ];
5655
5656                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5657                         programStats.score / 100.,
5658                         programStats.depth,
5659                         programStats.time / 100.,
5660                         (unsigned int)programStats.nodes,
5661                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5662                         programStats.movelist);
5663                 SendToICS(buf);
5664 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5665           }
5666         }
5667 #endif
5668         /* currentMoveString is set as a side-effect of ParseOneMove */
5669         strcpy(machineMove, currentMoveString);
5670         strcat(machineMove, "\n");
5671         strcpy(moveList[forwardMostMove], machineMove);
5672
5673         /* [AS] Save move info and clear stats for next move */
5674         pvInfoList[ forwardMostMove ].score = programStats.score;
5675         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5676         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5677         ClearProgramStats();
5678         thinkOutput[0] = NULLCHAR;
5679         hiddenThinkOutputState = 0;
5680
5681         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5682
5683         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5684         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5685             int count = 0;
5686
5687             while( count < adjudicateLossPlies ) {
5688                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5689
5690                 if( count & 1 ) {
5691                     score = -score; /* Flip score for winning side */
5692                 }
5693
5694                 if( score > adjudicateLossThreshold ) {
5695                     break;
5696                 }
5697
5698                 count++;
5699             }
5700
5701             if( count >= adjudicateLossPlies ) {
5702                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5703
5704                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5705                     "Xboard adjudication", 
5706                     GE_XBOARD );
5707
5708                 return;
5709             }
5710         }
5711
5712         if( gameMode == TwoMachinesPlay ) {
5713           // [HGM] some adjudications useful with buggy engines
5714             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5715           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5716
5717
5718             if( appData.testLegality )
5719             {   /* [HGM] Some more adjudications for obstinate engines */
5720                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5721                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5722                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5723                 static int moveCount = 6;
5724                 ChessMove result;
5725                 char *reason = NULL;
5726
5727                 /* Count what is on board. */
5728                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5729                 {   ChessSquare p = boards[forwardMostMove][i][j];
5730                     int m=i;
5731
5732                     switch((int) p)
5733                     {   /* count B,N,R and other of each side */
5734                         case WhiteKing:
5735                         case BlackKing:
5736                              NrK++; break; // [HGM] atomic: count Kings
5737                         case WhiteKnight:
5738                              NrWN++; break;
5739                         case WhiteBishop:
5740                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5741                              bishopsColor |= 1 << ((i^j)&1);
5742                              NrWB++; break;
5743                         case BlackKnight:
5744                              NrBN++; break;
5745                         case BlackBishop:
5746                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5747                              bishopsColor |= 1 << ((i^j)&1);
5748                              NrBB++; break;
5749                         case WhiteRook:
5750                              NrWR++; break;
5751                         case BlackRook:
5752                              NrBR++; break;
5753                         case WhiteQueen:
5754                              NrWQ++; break;
5755                         case BlackQueen:
5756                              NrBQ++; break;
5757                         case EmptySquare: 
5758                              break;
5759                         case BlackPawn:
5760                              m = 7-i;
5761                         case WhitePawn:
5762                              PawnAdvance += m; NrPawns++;
5763                     }
5764                     NrPieces += (p != EmptySquare);
5765                     NrW += ((int)p < (int)BlackPawn);
5766                     if(gameInfo.variant == VariantXiangqi && 
5767                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5768                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5769                         NrW -= ((int)p < (int)BlackPawn);
5770                     }
5771                 }
5772
5773                 /* Some material-based adjudications that have to be made before stalemate test */
5774                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5775                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5776                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5777                      if(appData.checkMates) {
5778                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5779                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5780                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5781                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5782                          return;
5783                      }
5784                 }
5785
5786                 /* Bare King in Shatranj (loses) or Losers (wins) */
5787                 if( NrW == 1 || NrPieces - NrW == 1) {
5788                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5789                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5790                      if(appData.checkMates) {
5791                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5792                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5793                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5794                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5795                          return;
5796                      }
5797                   } else
5798                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5799                   {    /* bare King */
5800                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5801                         if(appData.checkMates) {
5802                             /* but only adjudicate if adjudication enabled */
5803                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5804                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5805                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5806                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5807                             return;
5808                         }
5809                   }
5810                 } else bare = 1;
5811
5812
5813             // don't wait for engine to announce game end if we can judge ourselves
5814             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5815                                        castlingRights[forwardMostMove]) ) {
5816               case MT_CHECK:
5817                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5818                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5819                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5820                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5821                             checkCnt++;
5822                         if(checkCnt >= 2) {
5823                             reason = "Xboard adjudication: 3rd check";
5824                             epStatus[forwardMostMove] = EP_CHECKMATE;
5825                             break;
5826                         }
5827                     }
5828                 }
5829               case MT_NONE:
5830               default:
5831                 break;
5832               case MT_STALEMATE:
5833               case MT_STAINMATE:
5834                 reason = "Xboard adjudication: Stalemate";
5835                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5836                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5837                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5838                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5839                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5840                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5841                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5842                                                                         EP_CHECKMATE : EP_WINS);
5843                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5844                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5845                 }
5846                 break;
5847               case MT_CHECKMATE:
5848                 reason = "Xboard adjudication: Checkmate";
5849                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5850                 break;
5851             }
5852
5853                 switch(i = epStatus[forwardMostMove]) {
5854                     case EP_STALEMATE:
5855                         result = GameIsDrawn; break;
5856                     case EP_CHECKMATE:
5857                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5858                     case EP_WINS:
5859                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5860                     default:
5861                         result = (ChessMove) 0;
5862                 }
5863                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5864                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5865                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5866                     GameEnds( result, reason, GE_XBOARD );
5867                     return;
5868                 }
5869
5870                 /* Next absolutely insufficient mating material. */
5871                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5872                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5873                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5874                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5875                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5876
5877                      /* always flag draws, for judging claims */
5878                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5879
5880                      if(appData.materialDraws) {
5881                          /* but only adjudicate them if adjudication enabled */
5882                          SendToProgram("force\n", cps->other); // suppress reply
5883                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5884                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5885                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5886                          return;
5887                      }
5888                 }
5889
5890                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5891                 if(NrPieces == 4 && 
5892                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5893                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5894                    || NrWN==2 || NrBN==2     /* KNNK */
5895                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5896                   ) ) {
5897                      if(--moveCount < 0 && appData.trivialDraws)
5898                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5899                           SendToProgram("force\n", cps->other); // suppress reply
5900                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5901                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5902                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5903                           return;
5904                      }
5905                 } else moveCount = 6;
5906             }
5907           }
5908           
5909           if (appData.debugMode) { int i;
5910             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5911                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5912                     appData.drawRepeats);
5913             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5914               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5915             
5916           }
5917
5918                 /* Check for rep-draws */
5919                 count = 0;
5920                 for(k = forwardMostMove-2;
5921                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5922                         epStatus[k] < EP_UNKNOWN &&
5923                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5924                     k-=2)
5925                 {   int rights=0;
5926                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5927                         /* compare castling rights */
5928                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5929                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5930                                 rights++; /* King lost rights, while rook still had them */
5931                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5932                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5933                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5934                                    rights++; /* but at least one rook lost them */
5935                         }
5936                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5937                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5938                                 rights++; 
5939                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5940                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5941                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5942                                    rights++;
5943                         }
5944                         if( rights == 0 && ++count > appData.drawRepeats-2
5945                             && appData.drawRepeats > 1) {
5946                              /* adjudicate after user-specified nr of repeats */
5947                              SendToProgram("force\n", cps->other); // suppress reply
5948                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5949                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5950                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5951                                 // [HGM] xiangqi: check for forbidden perpetuals
5952                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5953                                 for(m=forwardMostMove; m>k; m-=2) {
5954                                     if(MateTest(boards[m], PosFlags(m), 
5955                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5956                                         ourPerpetual = 0; // the current mover did not always check
5957                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5958                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5959                                         hisPerpetual = 0; // the opponent did not always check
5960                                 }
5961                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5962                                                                         ourPerpetual, hisPerpetual);
5963                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5964                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5965                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5966                                     return;
5967                                 }
5968                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5969                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5970                                 // Now check for perpetual chases
5971                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5972                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5973                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5974                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5975                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5976                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5977                                         return;
5978                                     }
5979                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5980                                         break; // Abort repetition-checking loop.
5981                                 }
5982                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5983                              }
5984                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5985                              return;
5986                         }
5987                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5988                              epStatus[forwardMostMove] = EP_REP_DRAW;
5989                     }
5990                 }
5991
5992                 /* Now we test for 50-move draws. Determine ply count */
5993                 count = forwardMostMove;
5994                 /* look for last irreversble move */
5995                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5996                     count--;
5997                 /* if we hit starting position, add initial plies */
5998                 if( count == backwardMostMove )
5999                     count -= initialRulePlies;
6000                 count = forwardMostMove - count; 
6001                 if( count >= 100)
6002                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6003                          /* this is used to judge if draw claims are legal */
6004                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6005                          SendToProgram("force\n", cps->other); // suppress reply
6006                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6007                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6008                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6009                          return;
6010                 }
6011
6012                 /* if draw offer is pending, treat it as a draw claim
6013                  * when draw condition present, to allow engines a way to
6014                  * claim draws before making their move to avoid a race
6015                  * condition occurring after their move
6016                  */
6017                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6018                          char *p = NULL;
6019                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6020                              p = "Draw claim: 50-move rule";
6021                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6022                              p = "Draw claim: 3-fold repetition";
6023                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6024                              p = "Draw claim: insufficient mating material";
6025                          if( p != NULL ) {
6026                              SendToProgram("force\n", cps->other); // suppress reply
6027                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6028                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6029                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6030                              return;
6031                          }
6032                 }
6033
6034
6035                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6036                     SendToProgram("force\n", cps->other); // suppress reply
6037                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6038                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6039
6040                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6041
6042                     return;
6043                 }
6044         }
6045
6046         bookHit = NULL;
6047         if (gameMode == TwoMachinesPlay) {
6048             /* [HGM] relaying draw offers moved to after reception of move */
6049             /* and interpreting offer as claim if it brings draw condition */
6050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6051                 SendToProgram("draw\n", cps->other);
6052             }
6053             if (cps->other->sendTime) {
6054                 SendTimeRemaining(cps->other,
6055                                   cps->other->twoMachinesColor[0] == 'w');
6056             }
6057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6058             if (firstMove && !bookHit) {
6059                 firstMove = FALSE;
6060                 if (cps->other->useColors) {
6061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6062                 }
6063                 SendToProgram("go\n", cps->other);
6064             }
6065             cps->other->maybeThinking = TRUE;
6066         }
6067
6068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069         
6070         if (!pausing && appData.ringBellAfterMoves) {
6071             RingBell();
6072         }
6073
6074         /* 
6075          * Reenable menu items that were disabled while
6076          * machine was thinking
6077          */
6078         if (gameMode != TwoMachinesPlay)
6079             SetUserThinkingEnables();
6080
6081         // [HGM] book: after book hit opponent has received move and is now in force mode
6082         // force the book reply into it, and then fake that it outputted this move by jumping
6083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6084         if(bookHit) {
6085                 static char bookMove[MSG_SIZ]; // a bit generous?
6086
6087                 strcpy(bookMove, "move ");
6088                 strcat(bookMove, bookHit);
6089                 message = bookMove;
6090                 cps = cps->other;
6091                 programStats.nodes = programStats.depth = programStats.time = 
6092                 programStats.score = programStats.got_only_move = 0;
6093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6094
6095                 if(cps->lastPing != cps->lastPong) {
6096                     savedMessage = message; // args for deferred call
6097                     savedState = cps;
6098                     ScheduleDelayedEvent(DeferredBookMove, 10);
6099                     return;
6100                 }
6101                 goto FakeBookMove;
6102         }
6103
6104         return;
6105     }
6106
6107     /* Set special modes for chess engines.  Later something general
6108      *  could be added here; for now there is just one kludge feature,
6109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6110      *  when "xboard" is given as an interactive command.
6111      */
6112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6113         cps->useSigint = FALSE;
6114         cps->useSigterm = FALSE;
6115     }
6116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6117       ParseFeatures(message+8, cps);
6118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6119     }
6120
6121     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6122      * want this, I was asked to put it in, and obliged.
6123      */
6124     if (!strncmp(message, "setboard ", 9)) {
6125         Board initial_position; int i;
6126
6127         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6128
6129         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6130             DisplayError(_("Bad FEN received from engine"), 0);
6131             return ;
6132         } else {
6133            Reset(FALSE, FALSE);
6134            CopyBoard(boards[0], initial_position);
6135            initialRulePlies = FENrulePlies;
6136            epStatus[0] = FENepStatus;
6137            for( i=0; i<nrCastlingRights; i++ )
6138                 castlingRights[0][i] = FENcastlingRights[i];
6139            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6140            else gameMode = MachinePlaysBlack;                 
6141            DrawPosition(FALSE, boards[currentMove]);
6142         }
6143         return;
6144     }
6145
6146     /*
6147      * Look for communication commands
6148      */
6149     if (!strncmp(message, "telluser ", 9)) {
6150         DisplayNote(message + 9);
6151         return;
6152     }
6153     if (!strncmp(message, "tellusererror ", 14)) {
6154         DisplayError(message + 14, 0);
6155         return;
6156     }
6157     if (!strncmp(message, "tellopponent ", 13)) {
6158       if (appData.icsActive) {
6159         if (loggedOn) {
6160           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6161           SendToICS(buf1);
6162         }
6163       } else {
6164         DisplayNote(message + 13);
6165       }
6166       return;
6167     }
6168     if (!strncmp(message, "tellothers ", 11)) {
6169       if (appData.icsActive) {
6170         if (loggedOn) {
6171           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6172           SendToICS(buf1);
6173         }
6174       }
6175       return;
6176     }
6177     if (!strncmp(message, "tellall ", 8)) {
6178       if (appData.icsActive) {
6179         if (loggedOn) {
6180           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6181           SendToICS(buf1);
6182         }
6183       } else {
6184         DisplayNote(message + 8);
6185       }
6186       return;
6187     }
6188     if (strncmp(message, "warning", 7) == 0) {
6189         /* Undocumented feature, use tellusererror in new code */
6190         DisplayError(message, 0);
6191         return;
6192     }
6193     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6194         strcpy(realname, cps->tidy);
6195         strcat(realname, " query");
6196         AskQuestion(realname, buf2, buf1, cps->pr);
6197         return;
6198     }
6199     /* Commands from the engine directly to ICS.  We don't allow these to be 
6200      *  sent until we are logged on. Crafty kibitzes have been known to 
6201      *  interfere with the login process.
6202      */
6203     if (loggedOn) {
6204         if (!strncmp(message, "tellics ", 8)) {
6205             SendToICS(message + 8);
6206             SendToICS("\n");
6207             return;
6208         }
6209         if (!strncmp(message, "tellicsnoalias ", 15)) {
6210             SendToICS(ics_prefix);
6211             SendToICS(message + 15);
6212             SendToICS("\n");
6213             return;
6214         }
6215         /* The following are for backward compatibility only */
6216         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6217             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6218             SendToICS(ics_prefix);
6219             SendToICS(message);
6220             SendToICS("\n");
6221             return;
6222         }
6223     }
6224     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6225         return;
6226     }
6227     /*
6228      * If the move is illegal, cancel it and redraw the board.
6229      * Also deal with other error cases.  Matching is rather loose
6230      * here to accommodate engines written before the spec.
6231      */
6232     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6233         strncmp(message, "Error", 5) == 0) {
6234         if (StrStr(message, "name") || 
6235             StrStr(message, "rating") || StrStr(message, "?") ||
6236             StrStr(message, "result") || StrStr(message, "board") ||
6237             StrStr(message, "bk") || StrStr(message, "computer") ||
6238             StrStr(message, "variant") || StrStr(message, "hint") ||
6239             StrStr(message, "random") || StrStr(message, "depth") ||
6240             StrStr(message, "accepted")) {
6241             return;
6242         }
6243         if (StrStr(message, "protover")) {
6244           /* Program is responding to input, so it's apparently done
6245              initializing, and this error message indicates it is
6246              protocol version 1.  So we don't need to wait any longer
6247              for it to initialize and send feature commands. */
6248           FeatureDone(cps, 1);
6249           cps->protocolVersion = 1;
6250           return;
6251         }
6252         cps->maybeThinking = FALSE;
6253
6254         if (StrStr(message, "draw")) {
6255             /* Program doesn't have "draw" command */
6256             cps->sendDrawOffers = 0;
6257             return;
6258         }
6259         if (cps->sendTime != 1 &&
6260             (StrStr(message, "time") || StrStr(message, "otim"))) {
6261           /* Program apparently doesn't have "time" or "otim" command */
6262           cps->sendTime = 0;
6263           return;
6264         }
6265         if (StrStr(message, "analyze")) {
6266             cps->analysisSupport = FALSE;
6267             cps->analyzing = FALSE;
6268             Reset(FALSE, TRUE);
6269             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6270             DisplayError(buf2, 0);
6271             return;
6272         }
6273         if (StrStr(message, "(no matching move)st")) {
6274           /* Special kludge for GNU Chess 4 only */
6275           cps->stKludge = TRUE;
6276           SendTimeControl(cps, movesPerSession, timeControl,
6277                           timeIncrement, appData.searchDepth,
6278                           searchTime);
6279           return;
6280         }
6281         if (StrStr(message, "(no matching move)sd")) {
6282           /* Special kludge for GNU Chess 4 only */
6283           cps->sdKludge = TRUE;
6284           SendTimeControl(cps, movesPerSession, timeControl,
6285                           timeIncrement, appData.searchDepth,
6286                           searchTime);
6287           return;
6288         }
6289         if (!StrStr(message, "llegal")) {
6290             return;
6291         }
6292         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6293             gameMode == IcsIdle) return;
6294         if (forwardMostMove <= backwardMostMove) return;
6295         if (pausing) PauseEvent();
6296       if(appData.forceIllegal) {
6297             // [HGM] illegal: machine refused move; force position after move into it
6298           SendToProgram("force\n", cps);
6299           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6300                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6301                 // when black is to move, while there might be nothing on a2 or black
6302                 // might already have the move. So send the board as if white has the move.
6303                 // But first we must change the stm of the engine, as it refused the last move
6304                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6305                 if(WhiteOnMove(forwardMostMove)) {
6306                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6307                     SendBoard(cps, forwardMostMove); // kludgeless board
6308                 } else {
6309                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6310                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6311                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6312                 }
6313           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6314             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6315                  gameMode == TwoMachinesPlay)
6316               SendToProgram("go\n", cps);
6317             return;
6318       } else
6319         if (gameMode == PlayFromGameFile) {
6320             /* Stop reading this game file */
6321             gameMode = EditGame;
6322             ModeHighlight();
6323         }
6324         currentMove = --forwardMostMove;
6325         DisplayMove(currentMove-1); /* before DisplayMoveError */
6326         SwitchClocks();
6327         DisplayBothClocks();
6328         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6329                 parseList[currentMove], cps->which);
6330         DisplayMoveError(buf1);
6331         DrawPosition(FALSE, boards[currentMove]);
6332
6333         /* [HGM] illegal-move claim should forfeit game when Xboard */
6334         /* only passes fully legal moves                            */
6335         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6336             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6337                                 "False illegal-move claim", GE_XBOARD );
6338         }
6339         return;
6340     }
6341     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6342         /* Program has a broken "time" command that
6343            outputs a string not ending in newline.
6344            Don't use it. */
6345         cps->sendTime = 0;
6346     }
6347     
6348     /*
6349      * If chess program startup fails, exit with an error message.
6350      * Attempts to recover here are futile.
6351      */
6352     if ((StrStr(message, "unknown host") != NULL)
6353         || (StrStr(message, "No remote directory") != NULL)
6354         || (StrStr(message, "not found") != NULL)
6355         || (StrStr(message, "No such file") != NULL)
6356         || (StrStr(message, "can't alloc") != NULL)
6357         || (StrStr(message, "Permission denied") != NULL)) {
6358
6359         cps->maybeThinking = FALSE;
6360         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6361                 cps->which, cps->program, cps->host, message);
6362         RemoveInputSource(cps->isr);
6363         DisplayFatalError(buf1, 0, 1);
6364         return;
6365     }
6366     
6367     /* 
6368      * Look for hint output
6369      */
6370     if (sscanf(message, "Hint: %s", buf1) == 1) {
6371         if (cps == &first && hintRequested) {
6372             hintRequested = FALSE;
6373             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6374                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6375                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6376                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6377                                     fromY, fromX, toY, toX, promoChar, buf1);
6378                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6379                 DisplayInformation(buf2);
6380             } else {
6381                 /* Hint move could not be parsed!? */
6382               snprintf(buf2, sizeof(buf2),
6383                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6384                         buf1, cps->which);
6385                 DisplayError(buf2, 0);
6386             }
6387         } else {
6388             strcpy(lastHint, buf1);
6389         }
6390         return;
6391     }
6392
6393     /*
6394      * Ignore other messages if game is not in progress
6395      */
6396     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6397         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6398
6399     /*
6400      * look for win, lose, draw, or draw offer
6401      */
6402     if (strncmp(message, "1-0", 3) == 0) {
6403         char *p, *q, *r = "";
6404         p = strchr(message, '{');
6405         if (p) {
6406             q = strchr(p, '}');
6407             if (q) {
6408                 *q = NULLCHAR;
6409                 r = p + 1;
6410             }
6411         }
6412         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6413         return;
6414     } else if (strncmp(message, "0-1", 3) == 0) {
6415         char *p, *q, *r = "";
6416         p = strchr(message, '{');
6417         if (p) {
6418             q = strchr(p, '}');
6419             if (q) {
6420                 *q = NULLCHAR;
6421                 r = p + 1;
6422             }
6423         }
6424         /* Kludge for Arasan 4.1 bug */
6425         if (strcmp(r, "Black resigns") == 0) {
6426             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6427             return;
6428         }
6429         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6430         return;
6431     } else if (strncmp(message, "1/2", 3) == 0) {
6432         char *p, *q, *r = "";
6433         p = strchr(message, '{');
6434         if (p) {
6435             q = strchr(p, '}');
6436             if (q) {
6437                 *q = NULLCHAR;
6438                 r = p + 1;
6439             }
6440         }
6441             
6442         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6443         return;
6444
6445     } else if (strncmp(message, "White resign", 12) == 0) {
6446         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6447         return;
6448     } else if (strncmp(message, "Black resign", 12) == 0) {
6449         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6450         return;
6451     } else if (strncmp(message, "White matches", 13) == 0 ||
6452                strncmp(message, "Black matches", 13) == 0   ) {
6453         /* [HGM] ignore GNUShogi noises */
6454         return;
6455     } else if (strncmp(message, "White", 5) == 0 &&
6456                message[5] != '(' &&
6457                StrStr(message, "Black") == NULL) {
6458         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6459         return;
6460     } else if (strncmp(message, "Black", 5) == 0 &&
6461                message[5] != '(') {
6462         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6463         return;
6464     } else if (strcmp(message, "resign") == 0 ||
6465                strcmp(message, "computer resigns") == 0) {
6466         switch (gameMode) {
6467           case MachinePlaysBlack:
6468           case IcsPlayingBlack:
6469             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6470             break;
6471           case MachinePlaysWhite:
6472           case IcsPlayingWhite:
6473             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6474             break;
6475           case TwoMachinesPlay:
6476             if (cps->twoMachinesColor[0] == 'w')
6477               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6478             else
6479               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6480             break;
6481           default:
6482             /* can't happen */
6483             break;
6484         }
6485         return;
6486     } else if (strncmp(message, "opponent mates", 14) == 0) {
6487         switch (gameMode) {
6488           case MachinePlaysBlack:
6489           case IcsPlayingBlack:
6490             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6491             break;
6492           case MachinePlaysWhite:
6493           case IcsPlayingWhite:
6494             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6495             break;
6496           case TwoMachinesPlay:
6497             if (cps->twoMachinesColor[0] == 'w')
6498               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6499             else
6500               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6501             break;
6502           default:
6503             /* can't happen */
6504             break;
6505         }
6506         return;
6507     } else if (strncmp(message, "computer mates", 14) == 0) {
6508         switch (gameMode) {
6509           case MachinePlaysBlack:
6510           case IcsPlayingBlack:
6511             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6512             break;
6513           case MachinePlaysWhite:
6514           case IcsPlayingWhite:
6515             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6516             break;
6517           case TwoMachinesPlay:
6518             if (cps->twoMachinesColor[0] == 'w')
6519               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520             else
6521               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6522             break;
6523           default:
6524             /* can't happen */
6525             break;
6526         }
6527         return;
6528     } else if (strncmp(message, "checkmate", 9) == 0) {
6529         if (WhiteOnMove(forwardMostMove)) {
6530             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6531         } else {
6532             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6533         }
6534         return;
6535     } else if (strstr(message, "Draw") != NULL ||
6536                strstr(message, "game is a draw") != NULL) {
6537         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6538         return;
6539     } else if (strstr(message, "offer") != NULL &&
6540                strstr(message, "draw") != NULL) {
6541 #if ZIPPY
6542         if (appData.zippyPlay && first.initDone) {
6543             /* Relay offer to ICS */
6544             SendToICS(ics_prefix);
6545             SendToICS("draw\n");
6546         }
6547 #endif
6548         cps->offeredDraw = 2; /* valid until this engine moves twice */
6549         if (gameMode == TwoMachinesPlay) {
6550             if (cps->other->offeredDraw) {
6551                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6552             /* [HGM] in two-machine mode we delay relaying draw offer      */
6553             /* until after we also have move, to see if it is really claim */
6554             }
6555         } else if (gameMode == MachinePlaysWhite ||
6556                    gameMode == MachinePlaysBlack) {
6557           if (userOfferedDraw) {
6558             DisplayInformation(_("Machine accepts your draw offer"));
6559             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6560           } else {
6561             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6562           }
6563         }
6564     }
6565
6566     
6567     /*
6568      * Look for thinking output
6569      */
6570     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6571           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6572                                 ) {
6573         int plylev, mvleft, mvtot, curscore, time;
6574         char mvname[MOVE_LEN];
6575         u64 nodes; // [DM]
6576         char plyext;
6577         int ignore = FALSE;
6578         int prefixHint = FALSE;
6579         mvname[0] = NULLCHAR;
6580
6581         switch (gameMode) {
6582           case MachinePlaysBlack:
6583           case IcsPlayingBlack:
6584             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6585             break;
6586           case MachinePlaysWhite:
6587           case IcsPlayingWhite:
6588             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6589             break;
6590           case AnalyzeMode:
6591           case AnalyzeFile:
6592             break;
6593           case IcsObserving: /* [DM] icsEngineAnalyze */
6594             if (!appData.icsEngineAnalyze) ignore = TRUE;
6595             break;
6596           case TwoMachinesPlay:
6597             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6598                 ignore = TRUE;
6599             }
6600             break;
6601           default:
6602             ignore = TRUE;
6603             break;
6604         }
6605
6606         if (!ignore) {
6607             buf1[0] = NULLCHAR;
6608             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6609                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6610
6611                 if (plyext != ' ' && plyext != '\t') {
6612                     time *= 100;
6613                 }
6614
6615                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6616                 if( cps->scoreIsAbsolute && 
6617                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6618                 {
6619                     curscore = -curscore;
6620                 }
6621
6622
6623                 programStats.depth = plylev;
6624                 programStats.nodes = nodes;
6625                 programStats.time = time;
6626                 programStats.score = curscore;
6627                 programStats.got_only_move = 0;
6628
6629                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6630                         int ticklen;
6631
6632                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6633                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6634                         if(WhiteOnMove(forwardMostMove)) 
6635                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6636                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6637                 }
6638
6639                 /* Buffer overflow protection */
6640                 if (buf1[0] != NULLCHAR) {
6641                     if (strlen(buf1) >= sizeof(programStats.movelist)
6642                         && appData.debugMode) {
6643                         fprintf(debugFP,
6644                                 "PV is too long; using the first %d bytes.\n",
6645                                 sizeof(programStats.movelist) - 1);
6646                     }
6647
6648                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6649                 } else {
6650                     sprintf(programStats.movelist, " no PV\n");
6651                 }
6652
6653                 if (programStats.seen_stat) {
6654                     programStats.ok_to_send = 1;
6655                 }
6656
6657                 if (strchr(programStats.movelist, '(') != NULL) {
6658                     programStats.line_is_book = 1;
6659                     programStats.nr_moves = 0;
6660                     programStats.moves_left = 0;
6661                 } else {
6662                     programStats.line_is_book = 0;
6663                 }
6664
6665                 SendProgramStatsToFrontend( cps, &programStats );
6666
6667                 /* 
6668                     [AS] Protect the thinkOutput buffer from overflow... this
6669                     is only useful if buf1 hasn't overflowed first!
6670                 */
6671                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6672                         plylev, 
6673                         (gameMode == TwoMachinesPlay ?
6674                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6675                         ((double) curscore) / 100.0,
6676                         prefixHint ? lastHint : "",
6677                         prefixHint ? " " : "" );
6678
6679                 if( buf1[0] != NULLCHAR ) {
6680                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6681
6682                     if( strlen(buf1) > max_len ) {
6683                         if( appData.debugMode) {
6684                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6685                         }
6686                         buf1[max_len+1] = '\0';
6687                     }
6688
6689                     strcat( thinkOutput, buf1 );
6690                 }
6691
6692                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6693                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6694                     DisplayMove(currentMove - 1);
6695                     DisplayAnalysis();
6696                 }
6697                 return;
6698
6699             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6700                 /* crafty (9.25+) says "(only move) <move>"
6701                  * if there is only 1 legal move
6702                  */
6703                 sscanf(p, "(only move) %s", buf1);
6704                 sprintf(thinkOutput, "%s (only move)", buf1);
6705                 sprintf(programStats.movelist, "%s (only move)", buf1);
6706                 programStats.depth = 1;
6707                 programStats.nr_moves = 1;
6708                 programStats.moves_left = 1;
6709                 programStats.nodes = 1;
6710                 programStats.time = 1;
6711                 programStats.got_only_move = 1;
6712
6713                 /* Not really, but we also use this member to
6714                    mean "line isn't going to change" (Crafty
6715                    isn't searching, so stats won't change) */
6716                 programStats.line_is_book = 1;
6717
6718                 SendProgramStatsToFrontend( cps, &programStats );
6719                 
6720                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6721                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6722                     DisplayMove(currentMove - 1);
6723                     DisplayAnalysis();
6724                 }
6725                 return;
6726             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6727                               &time, &nodes, &plylev, &mvleft,
6728                               &mvtot, mvname) >= 5) {
6729                 /* The stat01: line is from Crafty (9.29+) in response
6730                    to the "." command */
6731                 programStats.seen_stat = 1;
6732                 cps->maybeThinking = TRUE;
6733
6734                 if (programStats.got_only_move || !appData.periodicUpdates)
6735                   return;
6736
6737                 programStats.depth = plylev;
6738                 programStats.time = time;
6739                 programStats.nodes = nodes;
6740                 programStats.moves_left = mvleft;
6741                 programStats.nr_moves = mvtot;
6742                 strcpy(programStats.move_name, mvname);
6743                 programStats.ok_to_send = 1;
6744                 programStats.movelist[0] = '\0';
6745
6746                 SendProgramStatsToFrontend( cps, &programStats );
6747
6748                 DisplayAnalysis();
6749                 return;
6750
6751             } else if (strncmp(message,"++",2) == 0) {
6752                 /* Crafty 9.29+ outputs this */
6753                 programStats.got_fail = 2;
6754                 return;
6755
6756             } else if (strncmp(message,"--",2) == 0) {
6757                 /* Crafty 9.29+ outputs this */
6758                 programStats.got_fail = 1;
6759                 return;
6760
6761             } else if (thinkOutput[0] != NULLCHAR &&
6762                        strncmp(message, "    ", 4) == 0) {
6763                 unsigned message_len;
6764
6765                 p = message;
6766                 while (*p && *p == ' ') p++;
6767
6768                 message_len = strlen( p );
6769
6770                 /* [AS] Avoid buffer overflow */
6771                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6772                     strcat(thinkOutput, " ");
6773                     strcat(thinkOutput, p);
6774                 }
6775
6776                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6777                     strcat(programStats.movelist, " ");
6778                     strcat(programStats.movelist, p);
6779                 }
6780
6781                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6782                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6783                     DisplayMove(currentMove - 1);
6784                     DisplayAnalysis();
6785                 }
6786                 return;
6787             }
6788         }
6789         else {
6790             buf1[0] = NULLCHAR;
6791
6792             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6793                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6794             {
6795                 ChessProgramStats cpstats;
6796
6797                 if (plyext != ' ' && plyext != '\t') {
6798                     time *= 100;
6799                 }
6800
6801                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6802                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6803                     curscore = -curscore;
6804                 }
6805
6806                 cpstats.depth = plylev;
6807                 cpstats.nodes = nodes;
6808                 cpstats.time = time;
6809                 cpstats.score = curscore;
6810                 cpstats.got_only_move = 0;
6811                 cpstats.movelist[0] = '\0';
6812
6813                 if (buf1[0] != NULLCHAR) {
6814                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6815                 }
6816
6817                 cpstats.ok_to_send = 0;
6818                 cpstats.line_is_book = 0;
6819                 cpstats.nr_moves = 0;
6820                 cpstats.moves_left = 0;
6821
6822                 SendProgramStatsToFrontend( cps, &cpstats );
6823             }
6824         }
6825     }
6826 }
6827
6828
6829 /* Parse a game score from the character string "game", and
6830    record it as the history of the current game.  The game
6831    score is NOT assumed to start from the standard position. 
6832    The display is not updated in any way.
6833    */
6834 void
6835 ParseGameHistory(game)
6836      char *game;
6837 {
6838     ChessMove moveType;
6839     int fromX, fromY, toX, toY, boardIndex;
6840     char promoChar;
6841     char *p, *q;
6842     char buf[MSG_SIZ];
6843
6844     if (appData.debugMode)
6845       fprintf(debugFP, "Parsing game history: %s\n", game);
6846
6847     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6848     gameInfo.site = StrSave(appData.icsHost);
6849     gameInfo.date = PGNDate();
6850     gameInfo.round = StrSave("-");
6851
6852     /* Parse out names of players */
6853     while (*game == ' ') game++;
6854     p = buf;
6855     while (*game != ' ') *p++ = *game++;
6856     *p = NULLCHAR;
6857     gameInfo.white = StrSave(buf);
6858     while (*game == ' ') game++;
6859     p = buf;
6860     while (*game != ' ' && *game != '\n') *p++ = *game++;
6861     *p = NULLCHAR;
6862     gameInfo.black = StrSave(buf);
6863
6864     /* Parse moves */
6865     boardIndex = blackPlaysFirst ? 1 : 0;
6866     yynewstr(game);
6867     for (;;) {
6868         yyboardindex = boardIndex;
6869         moveType = (ChessMove) yylex();
6870         switch (moveType) {
6871           case IllegalMove:             /* maybe suicide chess, etc. */
6872   if (appData.debugMode) {
6873     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6874     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6875     setbuf(debugFP, NULL);
6876   }
6877           case WhitePromotionChancellor:
6878           case BlackPromotionChancellor:
6879           case WhitePromotionArchbishop:
6880           case BlackPromotionArchbishop:
6881           case WhitePromotionQueen:
6882           case BlackPromotionQueen:
6883           case WhitePromotionRook:
6884           case BlackPromotionRook:
6885           case WhitePromotionBishop:
6886           case BlackPromotionBishop:
6887           case WhitePromotionKnight:
6888           case BlackPromotionKnight:
6889           case WhitePromotionKing:
6890           case BlackPromotionKing:
6891           case NormalMove:
6892           case WhiteCapturesEnPassant:
6893           case BlackCapturesEnPassant:
6894           case WhiteKingSideCastle:
6895           case WhiteQueenSideCastle:
6896           case BlackKingSideCastle:
6897           case BlackQueenSideCastle:
6898           case WhiteKingSideCastleWild:
6899           case WhiteQueenSideCastleWild:
6900           case BlackKingSideCastleWild:
6901           case BlackQueenSideCastleWild:
6902           /* PUSH Fabien */
6903           case WhiteHSideCastleFR:
6904           case WhiteASideCastleFR:
6905           case BlackHSideCastleFR:
6906           case BlackASideCastleFR:
6907           /* POP Fabien */
6908             fromX = currentMoveString[0] - AAA;
6909             fromY = currentMoveString[1] - ONE;
6910             toX = currentMoveString[2] - AAA;
6911             toY = currentMoveString[3] - ONE;
6912             promoChar = currentMoveString[4];
6913             break;
6914           case WhiteDrop:
6915           case BlackDrop:
6916             fromX = moveType == WhiteDrop ?
6917               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6918             (int) CharToPiece(ToLower(currentMoveString[0]));
6919             fromY = DROP_RANK;
6920             toX = currentMoveString[2] - AAA;
6921             toY = currentMoveString[3] - ONE;
6922             promoChar = NULLCHAR;
6923             break;
6924           case AmbiguousMove:
6925             /* bug? */
6926             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6927   if (appData.debugMode) {
6928     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6929     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6930     setbuf(debugFP, NULL);
6931   }
6932             DisplayError(buf, 0);
6933             return;
6934           case ImpossibleMove:
6935             /* bug? */
6936             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6937   if (appData.debugMode) {
6938     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6939     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6940     setbuf(debugFP, NULL);
6941   }
6942             DisplayError(buf, 0);
6943             return;
6944           case (ChessMove) 0:   /* end of file */
6945             if (boardIndex < backwardMostMove) {
6946                 /* Oops, gap.  How did that happen? */
6947                 DisplayError(_("Gap in move list"), 0);
6948                 return;
6949             }
6950             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6951             if (boardIndex > forwardMostMove) {
6952                 forwardMostMove = boardIndex;
6953             }
6954             return;
6955           case ElapsedTime:
6956             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6957                 strcat(parseList[boardIndex-1], " ");
6958                 strcat(parseList[boardIndex-1], yy_text);
6959             }
6960             continue;
6961           case Comment:
6962           case PGNTag:
6963           case NAG:
6964           default:
6965             /* ignore */
6966             continue;
6967           case WhiteWins:
6968           case BlackWins:
6969           case GameIsDrawn:
6970           case GameUnfinished:
6971             if (gameMode == IcsExamining) {
6972                 if (boardIndex < backwardMostMove) {
6973                     /* Oops, gap.  How did that happen? */
6974                     return;
6975                 }
6976                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6977                 return;
6978             }
6979             gameInfo.result = moveType;
6980             p = strchr(yy_text, '{');
6981             if (p == NULL) p = strchr(yy_text, '(');
6982             if (p == NULL) {
6983                 p = yy_text;
6984                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6985             } else {
6986                 q = strchr(p, *p == '{' ? '}' : ')');
6987                 if (q != NULL) *q = NULLCHAR;
6988                 p++;
6989             }
6990             gameInfo.resultDetails = StrSave(p);
6991             continue;
6992         }
6993         if (boardIndex >= forwardMostMove &&
6994             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6995             backwardMostMove = blackPlaysFirst ? 1 : 0;
6996             return;
6997         }
6998         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6999                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7000                                  parseList[boardIndex]);
7001         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7002         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7003         /* currentMoveString is set as a side-effect of yylex */
7004         strcpy(moveList[boardIndex], currentMoveString);
7005         strcat(moveList[boardIndex], "\n");
7006         boardIndex++;
7007         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7008                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7009         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7010                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7011           case MT_NONE:
7012           case MT_STALEMATE:
7013           default:
7014             break;
7015           case MT_CHECK:
7016             if(gameInfo.variant != VariantShogi)
7017                 strcat(parseList[boardIndex - 1], "+");
7018             break;
7019           case MT_CHECKMATE:
7020           case MT_STAINMATE:
7021             strcat(parseList[boardIndex - 1], "#");
7022             break;
7023         }
7024     }
7025 }
7026
7027
7028 /* Apply a move to the given board  */
7029 void
7030 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7031      int fromX, fromY, toX, toY;
7032      int promoChar;
7033      Board board;
7034      char *castling;
7035      char *ep;
7036 {
7037   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7038
7039     /* [HGM] compute & store e.p. status and castling rights for new position */
7040     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7041     { int i;
7042
7043       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7044       oldEP = *ep;
7045       *ep = EP_NONE;
7046
7047       if( board[toY][toX] != EmptySquare ) 
7048            *ep = EP_CAPTURE;  
7049
7050       if( board[fromY][fromX] == WhitePawn ) {
7051            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7052                *ep = EP_PAWN_MOVE;
7053            if( toY-fromY==2) {
7054                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7055                         gameInfo.variant != VariantBerolina || toX < fromX)
7056                       *ep = toX | berolina;
7057                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7058                         gameInfo.variant != VariantBerolina || toX > fromX) 
7059                       *ep = toX;
7060            }
7061       } else 
7062       if( board[fromY][fromX] == BlackPawn ) {
7063            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7064                *ep = EP_PAWN_MOVE; 
7065            if( toY-fromY== -2) {
7066                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7067                         gameInfo.variant != VariantBerolina || toX < fromX)
7068                       *ep = toX | berolina;
7069                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7070                         gameInfo.variant != VariantBerolina || toX > fromX) 
7071                       *ep = toX;
7072            }
7073        }
7074
7075        for(i=0; i<nrCastlingRights; i++) {
7076            if(castling[i] == fromX && castlingRank[i] == fromY ||
7077               castling[i] == toX   && castlingRank[i] == toY   
7078              ) castling[i] = -1; // revoke for moved or captured piece
7079        }
7080
7081     }
7082
7083   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7084   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7085        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7086          
7087   if (fromX == toX && fromY == toY) return;
7088
7089   if (fromY == DROP_RANK) {
7090         /* must be first */
7091         piece = board[toY][toX] = (ChessSquare) fromX;
7092   } else {
7093      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7094      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7095      if(gameInfo.variant == VariantKnightmate)
7096          king += (int) WhiteUnicorn - (int) WhiteKing;
7097
7098     /* Code added by Tord: */
7099     /* FRC castling assumed when king captures friendly rook. */
7100     if (board[fromY][fromX] == WhiteKing &&
7101              board[toY][toX] == WhiteRook) {
7102       board[fromY][fromX] = EmptySquare;
7103       board[toY][toX] = EmptySquare;
7104       if(toX > fromX) {
7105         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7106       } else {
7107         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7108       }
7109     } else if (board[fromY][fromX] == BlackKing &&
7110                board[toY][toX] == BlackRook) {
7111       board[fromY][fromX] = EmptySquare;
7112       board[toY][toX] = EmptySquare;
7113       if(toX > fromX) {
7114         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7115       } else {
7116         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7117       }
7118     /* End of code added by Tord */
7119
7120     } else if (board[fromY][fromX] == king
7121         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7122         && toY == fromY && toX > fromX+1) {
7123         board[fromY][fromX] = EmptySquare;
7124         board[toY][toX] = king;
7125         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7126         board[fromY][BOARD_RGHT-1] = EmptySquare;
7127     } else if (board[fromY][fromX] == king
7128         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7129                && toY == fromY && toX < fromX-1) {
7130         board[fromY][fromX] = EmptySquare;
7131         board[toY][toX] = king;
7132         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7133         board[fromY][BOARD_LEFT] = EmptySquare;
7134     } else if (board[fromY][fromX] == WhitePawn
7135                && toY == BOARD_HEIGHT-1
7136                && gameInfo.variant != VariantXiangqi
7137                ) {
7138         /* white pawn promotion */
7139         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7140         if (board[toY][toX] == EmptySquare) {
7141             board[toY][toX] = WhiteQueen;
7142         }
7143         if(gameInfo.variant==VariantBughouse ||
7144            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7145             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7146         board[fromY][fromX] = EmptySquare;
7147     } else if ((fromY == BOARD_HEIGHT-4)
7148                && (toX != fromX)
7149                && gameInfo.variant != VariantXiangqi
7150                && gameInfo.variant != VariantBerolina
7151                && (board[fromY][fromX] == WhitePawn)
7152                && (board[toY][toX] == EmptySquare)) {
7153         board[fromY][fromX] = EmptySquare;
7154         board[toY][toX] = WhitePawn;
7155         captured = board[toY - 1][toX];
7156         board[toY - 1][toX] = EmptySquare;
7157     } else if ((fromY == BOARD_HEIGHT-4)
7158                && (toX == fromX)
7159                && gameInfo.variant == VariantBerolina
7160                && (board[fromY][fromX] == WhitePawn)
7161                && (board[toY][toX] == EmptySquare)) {
7162         board[fromY][fromX] = EmptySquare;
7163         board[toY][toX] = WhitePawn;
7164         if(oldEP & EP_BEROLIN_A) {
7165                 captured = board[fromY][fromX-1];
7166                 board[fromY][fromX-1] = EmptySquare;
7167         }else{  captured = board[fromY][fromX+1];
7168                 board[fromY][fromX+1] = EmptySquare;
7169         }
7170     } else if (board[fromY][fromX] == king
7171         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7172                && toY == fromY && toX > fromX+1) {
7173         board[fromY][fromX] = EmptySquare;
7174         board[toY][toX] = king;
7175         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7176         board[fromY][BOARD_RGHT-1] = EmptySquare;
7177     } else if (board[fromY][fromX] == king
7178         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7179                && toY == fromY && toX < fromX-1) {
7180         board[fromY][fromX] = EmptySquare;
7181         board[toY][toX] = king;
7182         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7183         board[fromY][BOARD_LEFT] = EmptySquare;
7184     } else if (fromY == 7 && fromX == 3
7185                && board[fromY][fromX] == BlackKing
7186                && toY == 7 && toX == 5) {
7187         board[fromY][fromX] = EmptySquare;
7188         board[toY][toX] = BlackKing;
7189         board[fromY][7] = EmptySquare;
7190         board[toY][4] = BlackRook;
7191     } else if (fromY == 7 && fromX == 3
7192                && board[fromY][fromX] == BlackKing
7193                && toY == 7 && toX == 1) {
7194         board[fromY][fromX] = EmptySquare;
7195         board[toY][toX] = BlackKing;
7196         board[fromY][0] = EmptySquare;
7197         board[toY][2] = BlackRook;
7198     } else if (board[fromY][fromX] == BlackPawn
7199                && toY == 0
7200                && gameInfo.variant != VariantXiangqi
7201                ) {
7202         /* black pawn promotion */
7203         board[0][toX] = CharToPiece(ToLower(promoChar));
7204         if (board[0][toX] == EmptySquare) {
7205             board[0][toX] = BlackQueen;
7206         }
7207         if(gameInfo.variant==VariantBughouse ||
7208            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7209             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7210         board[fromY][fromX] = EmptySquare;
7211     } else if ((fromY == 3)
7212                && (toX != fromX)
7213                && gameInfo.variant != VariantXiangqi
7214                && gameInfo.variant != VariantBerolina
7215                && (board[fromY][fromX] == BlackPawn)
7216                && (board[toY][toX] == EmptySquare)) {
7217         board[fromY][fromX] = EmptySquare;
7218         board[toY][toX] = BlackPawn;
7219         captured = board[toY + 1][toX];
7220         board[toY + 1][toX] = EmptySquare;
7221     } else if ((fromY == 3)
7222                && (toX == fromX)
7223                && gameInfo.variant == VariantBerolina
7224                && (board[fromY][fromX] == BlackPawn)
7225                && (board[toY][toX] == EmptySquare)) {
7226         board[fromY][fromX] = EmptySquare;
7227         board[toY][toX] = BlackPawn;
7228         if(oldEP & EP_BEROLIN_A) {
7229                 captured = board[fromY][fromX-1];
7230                 board[fromY][fromX-1] = EmptySquare;
7231         }else{  captured = board[fromY][fromX+1];
7232                 board[fromY][fromX+1] = EmptySquare;
7233         }
7234     } else {
7235         board[toY][toX] = board[fromY][fromX];
7236         board[fromY][fromX] = EmptySquare;
7237     }
7238
7239     /* [HGM] now we promote for Shogi, if needed */
7240     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7241         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7242   }
7243
7244     if (gameInfo.holdingsWidth != 0) {
7245
7246       /* !!A lot more code needs to be written to support holdings  */
7247       /* [HGM] OK, so I have written it. Holdings are stored in the */
7248       /* penultimate board files, so they are automaticlly stored   */
7249       /* in the game history.                                       */
7250       if (fromY == DROP_RANK) {
7251         /* Delete from holdings, by decreasing count */
7252         /* and erasing image if necessary            */
7253         p = (int) fromX;
7254         if(p < (int) BlackPawn) { /* white drop */
7255              p -= (int)WhitePawn;
7256              if(p >= gameInfo.holdingsSize) p = 0;
7257              if(--board[p][BOARD_WIDTH-2] == 0)
7258                   board[p][BOARD_WIDTH-1] = EmptySquare;
7259         } else {                  /* black drop */
7260              p -= (int)BlackPawn;
7261              if(p >= gameInfo.holdingsSize) p = 0;
7262              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7263                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7264         }
7265       }
7266       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7267           && gameInfo.variant != VariantBughouse        ) {
7268         /* [HGM] holdings: Add to holdings, if holdings exist */
7269         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7270                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7271                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7272         }
7273         p = (int) captured;
7274         if (p >= (int) BlackPawn) {
7275           p -= (int)BlackPawn;
7276           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7277                   /* in Shogi restore piece to its original  first */
7278                   captured = (ChessSquare) (DEMOTED captured);
7279                   p = DEMOTED p;
7280           }
7281           p = PieceToNumber((ChessSquare)p);
7282           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7283           board[p][BOARD_WIDTH-2]++;
7284           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7285         } else {
7286           p -= (int)WhitePawn;
7287           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7288                   captured = (ChessSquare) (DEMOTED captured);
7289                   p = DEMOTED p;
7290           }
7291           p = PieceToNumber((ChessSquare)p);
7292           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7293           board[BOARD_HEIGHT-1-p][1]++;
7294           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7295         }
7296       }
7297
7298     } else if (gameInfo.variant == VariantAtomic) {
7299       if (captured != EmptySquare) {
7300         int y, x;
7301         for (y = toY-1; y <= toY+1; y++) {
7302           for (x = toX-1; x <= toX+1; x++) {
7303             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7304                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7305               board[y][x] = EmptySquare;
7306             }
7307           }
7308         }
7309         board[toY][toX] = EmptySquare;
7310       }
7311     }
7312     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7313         /* [HGM] Shogi promotions */
7314         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7315     }
7316
7317     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7318                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7319         // [HGM] superchess: take promotion piece out of holdings
7320         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7321         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7322             if(!--board[k][BOARD_WIDTH-2])
7323                 board[k][BOARD_WIDTH-1] = EmptySquare;
7324         } else {
7325             if(!--board[BOARD_HEIGHT-1-k][1])
7326                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7327         }
7328     }
7329
7330 }
7331
7332 /* Updates forwardMostMove */
7333 void
7334 MakeMove(fromX, fromY, toX, toY, promoChar)
7335      int fromX, fromY, toX, toY;
7336      int promoChar;
7337 {
7338 //    forwardMostMove++; // [HGM] bare: moved downstream
7339
7340     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7341         int timeLeft; static int lastLoadFlag=0; int king, piece;
7342         piece = boards[forwardMostMove][fromY][fromX];
7343         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7344         if(gameInfo.variant == VariantKnightmate)
7345             king += (int) WhiteUnicorn - (int) WhiteKing;
7346         if(forwardMostMove == 0) {
7347             if(blackPlaysFirst) 
7348                 fprintf(serverMoves, "%s;", second.tidy);
7349             fprintf(serverMoves, "%s;", first.tidy);
7350             if(!blackPlaysFirst) 
7351                 fprintf(serverMoves, "%s;", second.tidy);
7352         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7353         lastLoadFlag = loadFlag;
7354         // print base move
7355         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7356         // print castling suffix
7357         if( toY == fromY && piece == king ) {
7358             if(toX-fromX > 1)
7359                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7360             if(fromX-toX >1)
7361                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7362         }
7363         // e.p. suffix
7364         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7365              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7366              boards[forwardMostMove][toY][toX] == EmptySquare
7367              && fromX != toX )
7368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7369         // promotion suffix
7370         if(promoChar != NULLCHAR)
7371                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7372         if(!loadFlag) {
7373             fprintf(serverMoves, "/%d/%d",
7374                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7375             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7376             else                      timeLeft = blackTimeRemaining/1000;
7377             fprintf(serverMoves, "/%d", timeLeft);
7378         }
7379         fflush(serverMoves);
7380     }
7381
7382     if (forwardMostMove+1 >= MAX_MOVES) {
7383       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7384                         0, 1);
7385       return;
7386     }
7387     if (commentList[forwardMostMove+1] != NULL) {
7388         free(commentList[forwardMostMove+1]);
7389         commentList[forwardMostMove+1] = NULL;
7390     }
7391     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7392     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7393     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7394                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7395     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7396     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7397     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7398     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7399     gameInfo.result = GameUnfinished;
7400     if (gameInfo.resultDetails != NULL) {
7401         free(gameInfo.resultDetails);
7402         gameInfo.resultDetails = NULL;
7403     }
7404     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7405                               moveList[forwardMostMove - 1]);
7406     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7407                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7408                              fromY, fromX, toY, toX, promoChar,
7409                              parseList[forwardMostMove - 1]);
7410     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7411                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7412                             castlingRights[forwardMostMove]) ) {
7413       case MT_NONE:
7414       case MT_STALEMATE:
7415       default:
7416         break;
7417       case MT_CHECK:
7418         if(gameInfo.variant != VariantShogi)
7419             strcat(parseList[forwardMostMove - 1], "+");
7420         break;
7421       case MT_CHECKMATE:
7422       case MT_STAINMATE:
7423         strcat(parseList[forwardMostMove - 1], "#");
7424         break;
7425     }
7426     if (appData.debugMode) {
7427         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7428     }
7429
7430 }
7431
7432 /* Updates currentMove if not pausing */
7433 void
7434 ShowMove(fromX, fromY, toX, toY)
7435 {
7436     int instant = (gameMode == PlayFromGameFile) ?
7437         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7438     if(appData.noGUI) return;
7439     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7440         if (!instant) {
7441             if (forwardMostMove == currentMove + 1) {
7442                 AnimateMove(boards[forwardMostMove - 1],
7443                             fromX, fromY, toX, toY);
7444             }
7445             if (appData.highlightLastMove) {
7446                 SetHighlights(fromX, fromY, toX, toY);
7447             }
7448         }
7449         currentMove = forwardMostMove;
7450     }
7451
7452     if (instant) return;
7453
7454     DisplayMove(currentMove - 1);
7455     DrawPosition(FALSE, boards[currentMove]);
7456     DisplayBothClocks();
7457     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7458 }
7459
7460 void SendEgtPath(ChessProgramState *cps)
7461 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7462         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7463
7464         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7465
7466         while(*p) {
7467             char c, *q = name+1, *r, *s;
7468
7469             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7470             while(*p && *p != ',') *q++ = *p++;
7471             *q++ = ':'; *q = 0;
7472             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7473                 strcmp(name, ",nalimov:") == 0 ) {
7474                 // take nalimov path from the menu-changeable option first, if it is defined
7475                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7476                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7477             } else
7478             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7479                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7480                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7481                 s = r = StrStr(s, ":") + 1; // beginning of path info
7482                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7483                 c = *r; *r = 0;             // temporarily null-terminate path info
7484                     *--q = 0;               // strip of trailig ':' from name
7485                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7486                 *r = c;
7487                 SendToProgram(buf,cps);     // send egtbpath command for this format
7488             }
7489             if(*p == ',') p++; // read away comma to position for next format name
7490         }
7491 }
7492
7493 void
7494 InitChessProgram(cps, setup)
7495      ChessProgramState *cps;
7496      int setup; /* [HGM] needed to setup FRC opening position */
7497 {
7498     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7499     if (appData.noChessProgram) return;
7500     hintRequested = FALSE;
7501     bookRequested = FALSE;
7502
7503     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7504     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7505     if(cps->memSize) { /* [HGM] memory */
7506         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7507         SendToProgram(buf, cps);
7508     }
7509     SendEgtPath(cps); /* [HGM] EGT */
7510     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7511         sprintf(buf, "cores %d\n", appData.smpCores);
7512         SendToProgram(buf, cps);
7513     }
7514
7515     SendToProgram(cps->initString, cps);
7516     if (gameInfo.variant != VariantNormal &&
7517         gameInfo.variant != VariantLoadable
7518         /* [HGM] also send variant if board size non-standard */
7519         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7520                                             ) {
7521       char *v = VariantName(gameInfo.variant);
7522       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7523         /* [HGM] in protocol 1 we have to assume all variants valid */
7524         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7525         DisplayFatalError(buf, 0, 1);
7526         return;
7527       }
7528
7529       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7530       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7531       if( gameInfo.variant == VariantXiangqi )
7532            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7533       if( gameInfo.variant == VariantShogi )
7534            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7535       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7536            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7537       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7538                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7539            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7540       if( gameInfo.variant == VariantCourier )
7541            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7542       if( gameInfo.variant == VariantSuper )
7543            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7544       if( gameInfo.variant == VariantGreat )
7545            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7546
7547       if(overruled) {
7548            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7549                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7550            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7551            if(StrStr(cps->variants, b) == NULL) { 
7552                // specific sized variant not known, check if general sizing allowed
7553                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7554                    if(StrStr(cps->variants, "boardsize") == NULL) {
7555                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7556                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7557                        DisplayFatalError(buf, 0, 1);
7558                        return;
7559                    }
7560                    /* [HGM] here we really should compare with the maximum supported board size */
7561                }
7562            }
7563       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7564       sprintf(buf, "variant %s\n", b);
7565       SendToProgram(buf, cps);
7566     }
7567     currentlyInitializedVariant = gameInfo.variant;
7568
7569     /* [HGM] send opening position in FRC to first engine */
7570     if(setup) {
7571           SendToProgram("force\n", cps);
7572           SendBoard(cps, 0);
7573           /* engine is now in force mode! Set flag to wake it up after first move. */
7574           setboardSpoiledMachineBlack = 1;
7575     }
7576
7577     if (cps->sendICS) {
7578       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7579       SendToProgram(buf, cps);
7580     }
7581     cps->maybeThinking = FALSE;
7582     cps->offeredDraw = 0;
7583     if (!appData.icsActive) {
7584         SendTimeControl(cps, movesPerSession, timeControl,
7585                         timeIncrement, appData.searchDepth,
7586                         searchTime);
7587     }
7588     if (appData.showThinking 
7589         // [HGM] thinking: four options require thinking output to be sent
7590         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7591                                 ) {
7592         SendToProgram("post\n", cps);
7593     }
7594     SendToProgram("hard\n", cps);
7595     if (!appData.ponderNextMove) {
7596         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7597            it without being sure what state we are in first.  "hard"
7598            is not a toggle, so that one is OK.
7599          */
7600         SendToProgram("easy\n", cps);
7601     }
7602     if (cps->usePing) {
7603       sprintf(buf, "ping %d\n", ++cps->lastPing);
7604       SendToProgram(buf, cps);
7605     }
7606     cps->initDone = TRUE;
7607 }   
7608
7609
7610 void
7611 StartChessProgram(cps)
7612      ChessProgramState *cps;
7613 {
7614     char buf[MSG_SIZ];
7615     int err;
7616
7617     if (appData.noChessProgram) return;
7618     cps->initDone = FALSE;
7619
7620     if (strcmp(cps->host, "localhost") == 0) {
7621         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7622     } else if (*appData.remoteShell == NULLCHAR) {
7623         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7624     } else {
7625         if (*appData.remoteUser == NULLCHAR) {
7626           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7627                     cps->program);
7628         } else {
7629           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7630                     cps->host, appData.remoteUser, cps->program);
7631         }
7632         err = StartChildProcess(buf, "", &cps->pr);
7633     }
7634     
7635     if (err != 0) {
7636         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7637         DisplayFatalError(buf, err, 1);
7638         cps->pr = NoProc;
7639         cps->isr = NULL;
7640         return;
7641     }
7642     
7643     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7644     if (cps->protocolVersion > 1) {
7645       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7646       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7647       cps->comboCnt = 0;  //                and values of combo boxes
7648       SendToProgram(buf, cps);
7649     } else {
7650       SendToProgram("xboard\n", cps);
7651     }
7652 }
7653
7654
7655 void
7656 TwoMachinesEventIfReady P((void))
7657 {
7658   if (first.lastPing != first.lastPong) {
7659     DisplayMessage("", _("Waiting for first chess program"));
7660     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7661     return;
7662   }
7663   if (second.lastPing != second.lastPong) {
7664     DisplayMessage("", _("Waiting for second chess program"));
7665     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7666     return;
7667   }
7668   ThawUI();
7669   TwoMachinesEvent();
7670 }
7671
7672 void
7673 NextMatchGame P((void))
7674 {
7675     int index; /* [HGM] autoinc: step lod index during match */
7676     Reset(FALSE, TRUE);
7677     if (*appData.loadGameFile != NULLCHAR) {
7678         index = appData.loadGameIndex;
7679         if(index < 0) { // [HGM] autoinc
7680             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7681             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7682         } 
7683         LoadGameFromFile(appData.loadGameFile,
7684                          index,
7685                          appData.loadGameFile, FALSE);
7686     } else if (*appData.loadPositionFile != NULLCHAR) {
7687         index = appData.loadPositionIndex;
7688         if(index < 0) { // [HGM] autoinc
7689             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7690             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7691         } 
7692         LoadPositionFromFile(appData.loadPositionFile,
7693                              index,
7694                              appData.loadPositionFile);
7695     }
7696     TwoMachinesEventIfReady();
7697 }
7698
7699 void UserAdjudicationEvent( int result )
7700 {
7701     ChessMove gameResult = GameIsDrawn;
7702
7703     if( result > 0 ) {
7704         gameResult = WhiteWins;
7705     }
7706     else if( result < 0 ) {
7707         gameResult = BlackWins;
7708     }
7709
7710     if( gameMode == TwoMachinesPlay ) {
7711         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7712     }
7713 }
7714
7715
7716 // [HGM] save: calculate checksum of game to make games easily identifiable
7717 int StringCheckSum(char *s)
7718 {
7719         int i = 0;
7720         if(s==NULL) return 0;
7721         while(*s) i = i*259 + *s++;
7722         return i;
7723 }
7724
7725 int GameCheckSum()
7726 {
7727         int i, sum=0;
7728         for(i=backwardMostMove; i<forwardMostMove; i++) {
7729                 sum += pvInfoList[i].depth;
7730                 sum += StringCheckSum(parseList[i]);
7731                 sum += StringCheckSum(commentList[i]);
7732                 sum *= 261;
7733         }
7734         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7735         return sum + StringCheckSum(commentList[i]);
7736 } // end of save patch
7737
7738 void
7739 GameEnds(result, resultDetails, whosays)
7740      ChessMove result;
7741      char *resultDetails;
7742      int whosays;
7743 {
7744     GameMode nextGameMode;
7745     int isIcsGame;
7746     char buf[MSG_SIZ];
7747
7748     if(endingGame) return; /* [HGM] crash: forbid recursion */
7749     endingGame = 1;
7750
7751     if (appData.debugMode) {
7752       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7753               result, resultDetails ? resultDetails : "(null)", whosays);
7754     }
7755
7756     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7757         /* If we are playing on ICS, the server decides when the
7758            game is over, but the engine can offer to draw, claim 
7759            a draw, or resign. 
7760          */
7761 #if ZIPPY
7762         if (appData.zippyPlay && first.initDone) {
7763             if (result == GameIsDrawn) {
7764                 /* In case draw still needs to be claimed */
7765                 SendToICS(ics_prefix);
7766                 SendToICS("draw\n");
7767             } else if (StrCaseStr(resultDetails, "resign")) {
7768                 SendToICS(ics_prefix);
7769                 SendToICS("resign\n");
7770             }
7771         }
7772 #endif
7773         endingGame = 0; /* [HGM] crash */
7774         return;
7775     }
7776
7777     /* If we're loading the game from a file, stop */
7778     if (whosays == GE_FILE) {
7779       (void) StopLoadGameTimer();
7780       gameFileFP = NULL;
7781     }
7782
7783     /* Cancel draw offers */
7784     first.offeredDraw = second.offeredDraw = 0;
7785
7786     /* If this is an ICS game, only ICS can really say it's done;
7787        if not, anyone can. */
7788     isIcsGame = (gameMode == IcsPlayingWhite || 
7789                  gameMode == IcsPlayingBlack || 
7790                  gameMode == IcsObserving    || 
7791                  gameMode == IcsExamining);
7792
7793     if (!isIcsGame || whosays == GE_ICS) {
7794         /* OK -- not an ICS game, or ICS said it was done */
7795         StopClocks();
7796         if (!isIcsGame && !appData.noChessProgram) 
7797           SetUserThinkingEnables();
7798     
7799         /* [HGM] if a machine claims the game end we verify this claim */
7800         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7801             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7802                 char claimer;
7803                 ChessMove trueResult = (ChessMove) -1;
7804
7805                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7806                                             first.twoMachinesColor[0] :
7807                                             second.twoMachinesColor[0] ;
7808
7809                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7810                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7811                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7812                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7813                 } else
7814                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7815                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7816                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7817                 } else
7818                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7819                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7820                 }
7821
7822                 // now verify win claims, but not in drop games, as we don't understand those yet
7823                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7824                                                  || gameInfo.variant == VariantGreat) &&
7825                     (result == WhiteWins && claimer == 'w' ||
7826                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7827                       if (appData.debugMode) {
7828                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7829                                 result, epStatus[forwardMostMove], forwardMostMove);
7830                       }
7831                       if(result != trueResult) {
7832                               sprintf(buf, "False win claim: '%s'", resultDetails);
7833                               result = claimer == 'w' ? BlackWins : WhiteWins;
7834                               resultDetails = buf;
7835                       }
7836                 } else
7837                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7838                     && (forwardMostMove <= backwardMostMove ||
7839                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7840                         (claimer=='b')==(forwardMostMove&1))
7841                                                                                   ) {
7842                       /* [HGM] verify: draws that were not flagged are false claims */
7843                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7844                       result = claimer == 'w' ? BlackWins : WhiteWins;
7845                       resultDetails = buf;
7846                 }
7847                 /* (Claiming a loss is accepted no questions asked!) */
7848             }
7849             /* [HGM] bare: don't allow bare King to win */
7850             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7851                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7852                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7853                && result != GameIsDrawn)
7854             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7855                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7856                         int p = (int)boards[forwardMostMove][i][j] - color;
7857                         if(p >= 0 && p <= (int)WhiteKing) k++;
7858                 }
7859                 if (appData.debugMode) {
7860                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7861                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7862                 }
7863                 if(k <= 1) {
7864                         result = GameIsDrawn;
7865                         sprintf(buf, "%s but bare king", resultDetails);
7866                         resultDetails = buf;
7867                 }
7868             }
7869         }
7870
7871
7872         if(serverMoves != NULL && !loadFlag) { char c = '=';
7873             if(result==WhiteWins) c = '+';
7874             if(result==BlackWins) c = '-';
7875             if(resultDetails != NULL)
7876                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7877         }
7878         if (resultDetails != NULL) {
7879             gameInfo.result = result;
7880             gameInfo.resultDetails = StrSave(resultDetails);
7881
7882             /* display last move only if game was not loaded from file */
7883             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7884                 DisplayMove(currentMove - 1);
7885     
7886             if (forwardMostMove != 0) {
7887                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7888                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7889                                                                 ) {
7890                     if (*appData.saveGameFile != NULLCHAR) {
7891                         SaveGameToFile(appData.saveGameFile, TRUE);
7892                     } else if (appData.autoSaveGames) {
7893                         AutoSaveGame();
7894                     }
7895                     if (*appData.savePositionFile != NULLCHAR) {
7896                         SavePositionToFile(appData.savePositionFile);
7897                     }
7898                 }
7899             }
7900
7901             /* Tell program how game ended in case it is learning */
7902             /* [HGM] Moved this to after saving the PGN, just in case */
7903             /* engine died and we got here through time loss. In that */
7904             /* case we will get a fatal error writing the pipe, which */
7905             /* would otherwise lose us the PGN.                       */
7906             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7907             /* output during GameEnds should never be fatal anymore   */
7908             if (gameMode == MachinePlaysWhite ||
7909                 gameMode == MachinePlaysBlack ||
7910                 gameMode == TwoMachinesPlay ||
7911                 gameMode == IcsPlayingWhite ||
7912                 gameMode == IcsPlayingBlack ||
7913                 gameMode == BeginningOfGame) {
7914                 char buf[MSG_SIZ];
7915                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7916                         resultDetails);
7917                 if (first.pr != NoProc) {
7918                     SendToProgram(buf, &first);
7919                 }
7920                 if (second.pr != NoProc &&
7921                     gameMode == TwoMachinesPlay) {
7922                     SendToProgram(buf, &second);
7923                 }
7924             }
7925         }
7926
7927         if (appData.icsActive) {
7928             if (appData.quietPlay &&
7929                 (gameMode == IcsPlayingWhite ||
7930                  gameMode == IcsPlayingBlack)) {
7931                 SendToICS(ics_prefix);
7932                 SendToICS("set shout 1\n");
7933             }
7934             nextGameMode = IcsIdle;
7935             ics_user_moved = FALSE;
7936             /* clean up premove.  It's ugly when the game has ended and the
7937              * premove highlights are still on the board.
7938              */
7939             if (gotPremove) {
7940               gotPremove = FALSE;
7941               ClearPremoveHighlights();
7942               DrawPosition(FALSE, boards[currentMove]);
7943             }
7944             if (whosays == GE_ICS) {
7945                 switch (result) {
7946                 case WhiteWins:
7947                     if (gameMode == IcsPlayingWhite)
7948                         PlayIcsWinSound();
7949                     else if(gameMode == IcsPlayingBlack)
7950                         PlayIcsLossSound();
7951                     break;
7952                 case BlackWins:
7953                     if (gameMode == IcsPlayingBlack)
7954                         PlayIcsWinSound();
7955                     else if(gameMode == IcsPlayingWhite)
7956                         PlayIcsLossSound();
7957                     break;
7958                 case GameIsDrawn:
7959                     PlayIcsDrawSound();
7960                     break;
7961                 default:
7962                     PlayIcsUnfinishedSound();
7963                 }
7964             }
7965         } else if (gameMode == EditGame ||
7966                    gameMode == PlayFromGameFile || 
7967                    gameMode == AnalyzeMode || 
7968                    gameMode == AnalyzeFile) {
7969             nextGameMode = gameMode;
7970         } else {
7971             nextGameMode = EndOfGame;
7972         }
7973         pausing = FALSE;
7974         ModeHighlight();
7975     } else {
7976         nextGameMode = gameMode;
7977     }
7978
7979     if (appData.noChessProgram) {
7980         gameMode = nextGameMode;
7981         ModeHighlight();
7982         endingGame = 0; /* [HGM] crash */
7983         return;
7984     }
7985
7986     if (first.reuse) {
7987         /* Put first chess program into idle state */
7988         if (first.pr != NoProc &&
7989             (gameMode == MachinePlaysWhite ||
7990              gameMode == MachinePlaysBlack ||
7991              gameMode == TwoMachinesPlay ||
7992              gameMode == IcsPlayingWhite ||
7993              gameMode == IcsPlayingBlack ||
7994              gameMode == BeginningOfGame)) {
7995             SendToProgram("force\n", &first);
7996             if (first.usePing) {
7997               char buf[MSG_SIZ];
7998               sprintf(buf, "ping %d\n", ++first.lastPing);
7999               SendToProgram(buf, &first);
8000             }
8001         }
8002     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8003         /* Kill off first chess program */
8004         if (first.isr != NULL)
8005           RemoveInputSource(first.isr);
8006         first.isr = NULL;
8007     
8008         if (first.pr != NoProc) {
8009             ExitAnalyzeMode();
8010             DoSleep( appData.delayBeforeQuit );
8011             SendToProgram("quit\n", &first);
8012             DoSleep( appData.delayAfterQuit );
8013             DestroyChildProcess(first.pr, first.useSigterm);
8014         }
8015         first.pr = NoProc;
8016     }
8017     if (second.reuse) {
8018         /* Put second chess program into idle state */
8019         if (second.pr != NoProc &&
8020             gameMode == TwoMachinesPlay) {
8021             SendToProgram("force\n", &second);
8022             if (second.usePing) {
8023               char buf[MSG_SIZ];
8024               sprintf(buf, "ping %d\n", ++second.lastPing);
8025               SendToProgram(buf, &second);
8026             }
8027         }
8028     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8029         /* Kill off second chess program */
8030         if (second.isr != NULL)
8031           RemoveInputSource(second.isr);
8032         second.isr = NULL;
8033     
8034         if (second.pr != NoProc) {
8035             DoSleep( appData.delayBeforeQuit );
8036             SendToProgram("quit\n", &second);
8037             DoSleep( appData.delayAfterQuit );
8038             DestroyChildProcess(second.pr, second.useSigterm);
8039         }
8040         second.pr = NoProc;
8041     }
8042
8043     if (matchMode && gameMode == TwoMachinesPlay) {
8044         switch (result) {
8045         case WhiteWins:
8046           if (first.twoMachinesColor[0] == 'w') {
8047             first.matchWins++;
8048           } else {
8049             second.matchWins++;
8050           }
8051           break;
8052         case BlackWins:
8053           if (first.twoMachinesColor[0] == 'b') {
8054             first.matchWins++;
8055           } else {
8056             second.matchWins++;
8057           }
8058           break;
8059         default:
8060           break;
8061         }
8062         if (matchGame < appData.matchGames) {
8063             char *tmp;
8064             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8065                 tmp = first.twoMachinesColor;
8066                 first.twoMachinesColor = second.twoMachinesColor;
8067                 second.twoMachinesColor = tmp;
8068             }
8069             gameMode = nextGameMode;
8070             matchGame++;
8071             if(appData.matchPause>10000 || appData.matchPause<10)
8072                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8073             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8074             endingGame = 0; /* [HGM] crash */
8075             return;
8076         } else {
8077             char buf[MSG_SIZ];
8078             gameMode = nextGameMode;
8079             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8080                     first.tidy, second.tidy,
8081                     first.matchWins, second.matchWins,
8082                     appData.matchGames - (first.matchWins + second.matchWins));
8083             DisplayFatalError(buf, 0, 0);
8084         }
8085     }
8086     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8087         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8088       ExitAnalyzeMode();
8089     gameMode = nextGameMode;
8090     ModeHighlight();
8091     endingGame = 0;  /* [HGM] crash */
8092 }
8093
8094 /* Assumes program was just initialized (initString sent).
8095    Leaves program in force mode. */
8096 void
8097 FeedMovesToProgram(cps, upto) 
8098      ChessProgramState *cps;
8099      int upto;
8100 {
8101     int i;
8102     
8103     if (appData.debugMode)
8104       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8105               startedFromSetupPosition ? "position and " : "",
8106               backwardMostMove, upto, cps->which);
8107     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8108         // [HGM] variantswitch: make engine aware of new variant
8109         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8110                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8111         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8112         SendToProgram(buf, cps);
8113         currentlyInitializedVariant = gameInfo.variant;
8114     }
8115     SendToProgram("force\n", cps);
8116     if (startedFromSetupPosition) {
8117         SendBoard(cps, backwardMostMove);
8118     if (appData.debugMode) {
8119         fprintf(debugFP, "feedMoves\n");
8120     }
8121     }
8122     for (i = backwardMostMove; i < upto; i++) {
8123         SendMoveToProgram(i, cps);
8124     }
8125 }
8126
8127
8128 void
8129 ResurrectChessProgram()
8130 {
8131      /* The chess program may have exited.
8132         If so, restart it and feed it all the moves made so far. */
8133
8134     if (appData.noChessProgram || first.pr != NoProc) return;
8135     
8136     StartChessProgram(&first);
8137     InitChessProgram(&first, FALSE);
8138     FeedMovesToProgram(&first, currentMove);
8139
8140     if (!first.sendTime) {
8141         /* can't tell gnuchess what its clock should read,
8142            so we bow to its notion. */
8143         ResetClocks();
8144         timeRemaining[0][currentMove] = whiteTimeRemaining;
8145         timeRemaining[1][currentMove] = blackTimeRemaining;
8146     }
8147
8148     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8149                 appData.icsEngineAnalyze) && first.analysisSupport) {
8150       SendToProgram("analyze\n", &first);
8151       first.analyzing = TRUE;
8152     }
8153 }
8154
8155 /*
8156  * Button procedures
8157  */
8158 void
8159 Reset(redraw, init)
8160      int redraw, init;
8161 {
8162     int i;
8163
8164     if (appData.debugMode) {
8165         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8166                 redraw, init, gameMode);
8167     }
8168     pausing = pauseExamInvalid = FALSE;
8169     startedFromSetupPosition = blackPlaysFirst = FALSE;
8170     firstMove = TRUE;
8171     whiteFlag = blackFlag = FALSE;
8172     userOfferedDraw = FALSE;
8173     hintRequested = bookRequested = FALSE;
8174     first.maybeThinking = FALSE;
8175     second.maybeThinking = FALSE;
8176     first.bookSuspend = FALSE; // [HGM] book
8177     second.bookSuspend = FALSE;
8178     thinkOutput[0] = NULLCHAR;
8179     lastHint[0] = NULLCHAR;
8180     ClearGameInfo(&gameInfo);
8181     gameInfo.variant = StringToVariant(appData.variant);
8182     ics_user_moved = ics_clock_paused = FALSE;
8183     ics_getting_history = H_FALSE;
8184     ics_gamenum = -1;
8185     white_holding[0] = black_holding[0] = NULLCHAR;
8186     ClearProgramStats();
8187     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8188     
8189     ResetFrontEnd();
8190     ClearHighlights();
8191     flipView = appData.flipView;
8192     ClearPremoveHighlights();
8193     gotPremove = FALSE;
8194     alarmSounded = FALSE;
8195
8196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8197     if(appData.serverMovesName != NULL) {
8198         /* [HGM] prepare to make moves file for broadcasting */
8199         clock_t t = clock();
8200         if(serverMoves != NULL) fclose(serverMoves);
8201         serverMoves = fopen(appData.serverMovesName, "r");
8202         if(serverMoves != NULL) {
8203             fclose(serverMoves);
8204             /* delay 15 sec before overwriting, so all clients can see end */
8205             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8206         }
8207         serverMoves = fopen(appData.serverMovesName, "w");
8208     }
8209
8210     ExitAnalyzeMode();
8211     gameMode = BeginningOfGame;
8212     ModeHighlight();
8213     if(appData.icsActive) gameInfo.variant = VariantNormal;
8214     currentMove = forwardMostMove = backwardMostMove = 0;
8215     InitPosition(redraw);
8216     for (i = 0; i < MAX_MOVES; i++) {
8217         if (commentList[i] != NULL) {
8218             free(commentList[i]);
8219             commentList[i] = NULL;
8220         }
8221     }
8222     ResetClocks();
8223     timeRemaining[0][0] = whiteTimeRemaining;
8224     timeRemaining[1][0] = blackTimeRemaining;
8225     if (first.pr == NULL) {
8226         StartChessProgram(&first);
8227     }
8228     if (init) {
8229             InitChessProgram(&first, startedFromSetupPosition);
8230     }
8231     DisplayTitle("");
8232     DisplayMessage("", "");
8233     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8234     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8235 }
8236
8237 void
8238 AutoPlayGameLoop()
8239 {
8240     for (;;) {
8241         if (!AutoPlayOneMove())
8242           return;
8243         if (matchMode || appData.timeDelay == 0)
8244           continue;
8245         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8246           return;
8247         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8248         break;
8249     }
8250 }
8251
8252
8253 int
8254 AutoPlayOneMove()
8255 {
8256     int fromX, fromY, toX, toY;
8257
8258     if (appData.debugMode) {
8259       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8260     }
8261
8262     if (gameMode != PlayFromGameFile)
8263       return FALSE;
8264
8265     if (currentMove >= forwardMostMove) {
8266       gameMode = EditGame;
8267       ModeHighlight();
8268
8269       /* [AS] Clear current move marker at the end of a game */
8270       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8271
8272       return FALSE;
8273     }
8274     
8275     toX = moveList[currentMove][2] - AAA;
8276     toY = moveList[currentMove][3] - ONE;
8277
8278     if (moveList[currentMove][1] == '@') {
8279         if (appData.highlightLastMove) {
8280             SetHighlights(-1, -1, toX, toY);
8281         }
8282     } else {
8283         fromX = moveList[currentMove][0] - AAA;
8284         fromY = moveList[currentMove][1] - ONE;
8285
8286         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8287
8288         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8289
8290         if (appData.highlightLastMove) {
8291             SetHighlights(fromX, fromY, toX, toY);
8292         }
8293     }
8294     DisplayMove(currentMove);
8295     SendMoveToProgram(currentMove++, &first);
8296     DisplayBothClocks();
8297     DrawPosition(FALSE, boards[currentMove]);
8298     // [HGM] PV info: always display, routine tests if empty
8299     DisplayComment(currentMove - 1, commentList[currentMove]);
8300     return TRUE;
8301 }
8302
8303
8304 int
8305 LoadGameOneMove(readAhead)
8306      ChessMove readAhead;
8307 {
8308     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8309     char promoChar = NULLCHAR;
8310     ChessMove moveType;
8311     char move[MSG_SIZ];
8312     char *p, *q;
8313     
8314     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8315         gameMode != AnalyzeMode && gameMode != Training) {
8316         gameFileFP = NULL;
8317         return FALSE;
8318     }
8319     
8320     yyboardindex = forwardMostMove;
8321     if (readAhead != (ChessMove)0) {
8322       moveType = readAhead;
8323     } else {
8324       if (gameFileFP == NULL)
8325           return FALSE;
8326       moveType = (ChessMove) yylex();
8327     }
8328     
8329     done = FALSE;
8330     switch (moveType) {
8331       case Comment:
8332         if (appData.debugMode) 
8333           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8334         p = yy_text;
8335         if (*p == '{' || *p == '[' || *p == '(') {
8336             p[strlen(p) - 1] = NULLCHAR;
8337             p++;
8338         }
8339
8340         /* append the comment but don't display it */
8341         while (*p == '\n') p++;
8342         AppendComment(currentMove, p);
8343         return TRUE;
8344
8345       case WhiteCapturesEnPassant:
8346       case BlackCapturesEnPassant:
8347       case WhitePromotionChancellor:
8348       case BlackPromotionChancellor:
8349       case WhitePromotionArchbishop:
8350       case BlackPromotionArchbishop:
8351       case WhitePromotionCentaur:
8352       case BlackPromotionCentaur:
8353       case WhitePromotionQueen:
8354       case BlackPromotionQueen:
8355       case WhitePromotionRook:
8356       case BlackPromotionRook:
8357       case WhitePromotionBishop:
8358       case BlackPromotionBishop:
8359       case WhitePromotionKnight:
8360       case BlackPromotionKnight:
8361       case WhitePromotionKing:
8362       case BlackPromotionKing:
8363       case NormalMove:
8364       case WhiteKingSideCastle:
8365       case WhiteQueenSideCastle:
8366       case BlackKingSideCastle:
8367       case BlackQueenSideCastle:
8368       case WhiteKingSideCastleWild:
8369       case WhiteQueenSideCastleWild:
8370       case BlackKingSideCastleWild:
8371       case BlackQueenSideCastleWild:
8372       /* PUSH Fabien */
8373       case WhiteHSideCastleFR:
8374       case WhiteASideCastleFR:
8375       case BlackHSideCastleFR:
8376       case BlackASideCastleFR:
8377       /* POP Fabien */
8378         if (appData.debugMode)
8379           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8380         fromX = currentMoveString[0] - AAA;
8381         fromY = currentMoveString[1] - ONE;
8382         toX = currentMoveString[2] - AAA;
8383         toY = currentMoveString[3] - ONE;
8384         promoChar = currentMoveString[4];
8385         break;
8386
8387       case WhiteDrop:
8388       case BlackDrop:
8389         if (appData.debugMode)
8390           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8391         fromX = moveType == WhiteDrop ?
8392           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8393         (int) CharToPiece(ToLower(currentMoveString[0]));
8394         fromY = DROP_RANK;
8395         toX = currentMoveString[2] - AAA;
8396         toY = currentMoveString[3] - ONE;
8397         break;
8398
8399       case WhiteWins:
8400       case BlackWins:
8401       case GameIsDrawn:
8402       case GameUnfinished:
8403         if (appData.debugMode)
8404           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8405         p = strchr(yy_text, '{');
8406         if (p == NULL) p = strchr(yy_text, '(');
8407         if (p == NULL) {
8408             p = yy_text;
8409             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8410         } else {
8411             q = strchr(p, *p == '{' ? '}' : ')');
8412             if (q != NULL) *q = NULLCHAR;
8413             p++;
8414         }
8415         GameEnds(moveType, p, GE_FILE);
8416         done = TRUE;
8417         if (cmailMsgLoaded) {
8418             ClearHighlights();
8419             flipView = WhiteOnMove(currentMove);
8420             if (moveType == GameUnfinished) flipView = !flipView;
8421             if (appData.debugMode)
8422               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8423         }
8424         break;
8425
8426       case (ChessMove) 0:       /* end of file */
8427         if (appData.debugMode)
8428           fprintf(debugFP, "Parser hit end of file\n");
8429         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8430                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8431           case MT_NONE:
8432           case MT_CHECK:
8433             break;
8434           case MT_CHECKMATE:
8435           case MT_STAINMATE:
8436             if (WhiteOnMove(currentMove)) {
8437                 GameEnds(BlackWins, "Black mates", GE_FILE);
8438             } else {
8439                 GameEnds(WhiteWins, "White mates", GE_FILE);
8440             }
8441             break;
8442           case MT_STALEMATE:
8443             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8444             break;
8445         }
8446         done = TRUE;
8447         break;
8448
8449       case MoveNumberOne:
8450         if (lastLoadGameStart == GNUChessGame) {
8451             /* GNUChessGames have numbers, but they aren't move numbers */
8452             if (appData.debugMode)
8453               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8454                       yy_text, (int) moveType);
8455             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8456         }
8457         /* else fall thru */
8458
8459       case XBoardGame:
8460       case GNUChessGame:
8461       case PGNTag:
8462         /* Reached start of next game in file */
8463         if (appData.debugMode)
8464           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8465         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8466                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8467           case MT_NONE:
8468           case MT_CHECK:
8469             break;
8470           case MT_CHECKMATE:
8471           case MT_STAINMATE:
8472             if (WhiteOnMove(currentMove)) {
8473                 GameEnds(BlackWins, "Black mates", GE_FILE);
8474             } else {
8475                 GameEnds(WhiteWins, "White mates", GE_FILE);
8476             }
8477             break;
8478           case MT_STALEMATE:
8479             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8480             break;
8481         }
8482         done = TRUE;
8483         break;
8484
8485       case PositionDiagram:     /* should not happen; ignore */
8486       case ElapsedTime:         /* ignore */
8487       case NAG:                 /* ignore */
8488         if (appData.debugMode)
8489           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8490                   yy_text, (int) moveType);
8491         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8492
8493       case IllegalMove:
8494         if (appData.testLegality) {
8495             if (appData.debugMode)
8496               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8497             sprintf(move, _("Illegal move: %d.%s%s"),
8498                     (forwardMostMove / 2) + 1,
8499                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8500             DisplayError(move, 0);
8501             done = TRUE;
8502         } else {
8503             if (appData.debugMode)
8504               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8505                       yy_text, currentMoveString);
8506             fromX = currentMoveString[0] - AAA;
8507             fromY = currentMoveString[1] - ONE;
8508             toX = currentMoveString[2] - AAA;
8509             toY = currentMoveString[3] - ONE;
8510             promoChar = currentMoveString[4];
8511         }
8512         break;
8513
8514       case AmbiguousMove:
8515         if (appData.debugMode)
8516           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8517         sprintf(move, _("Ambiguous move: %d.%s%s"),
8518                 (forwardMostMove / 2) + 1,
8519                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8520         DisplayError(move, 0);
8521         done = TRUE;
8522         break;
8523
8524       default:
8525       case ImpossibleMove:
8526         if (appData.debugMode)
8527           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8528         sprintf(move, _("Illegal move: %d.%s%s"),
8529                 (forwardMostMove / 2) + 1,
8530                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8531         DisplayError(move, 0);
8532         done = TRUE;
8533         break;
8534     }
8535
8536     if (done) {
8537         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8538             DrawPosition(FALSE, boards[currentMove]);
8539             DisplayBothClocks();
8540             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8541               DisplayComment(currentMove - 1, commentList[currentMove]);
8542         }
8543         (void) StopLoadGameTimer();
8544         gameFileFP = NULL;
8545         cmailOldMove = forwardMostMove;
8546         return FALSE;
8547     } else {
8548         /* currentMoveString is set as a side-effect of yylex */
8549         strcat(currentMoveString, "\n");
8550         strcpy(moveList[forwardMostMove], currentMoveString);
8551         
8552         thinkOutput[0] = NULLCHAR;
8553         MakeMove(fromX, fromY, toX, toY, promoChar);
8554         currentMove = forwardMostMove;
8555         return TRUE;
8556     }
8557 }
8558
8559 /* Load the nth game from the given file */
8560 int
8561 LoadGameFromFile(filename, n, title, useList)
8562      char *filename;
8563      int n;
8564      char *title;
8565      /*Boolean*/ int useList;
8566 {
8567     FILE *f;
8568     char buf[MSG_SIZ];
8569
8570     if (strcmp(filename, "-") == 0) {
8571         f = stdin;
8572         title = "stdin";
8573     } else {
8574         f = fopen(filename, "rb");
8575         if (f == NULL) {
8576           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8577             DisplayError(buf, errno);
8578             return FALSE;
8579         }
8580     }
8581     if (fseek(f, 0, 0) == -1) {
8582         /* f is not seekable; probably a pipe */
8583         useList = FALSE;
8584     }
8585     if (useList && n == 0) {
8586         int error = GameListBuild(f);
8587         if (error) {
8588             DisplayError(_("Cannot build game list"), error);
8589         } else if (!ListEmpty(&gameList) &&
8590                    ((ListGame *) gameList.tailPred)->number > 1) {
8591             GameListPopUp(f, title);
8592             return TRUE;
8593         }
8594         GameListDestroy();
8595         n = 1;
8596     }
8597     if (n == 0) n = 1;
8598     return LoadGame(f, n, title, FALSE);
8599 }
8600
8601
8602 void
8603 MakeRegisteredMove()
8604 {
8605     int fromX, fromY, toX, toY;
8606     char promoChar;
8607     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8608         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8609           case CMAIL_MOVE:
8610           case CMAIL_DRAW:
8611             if (appData.debugMode)
8612               fprintf(debugFP, "Restoring %s for game %d\n",
8613                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8614     
8615             thinkOutput[0] = NULLCHAR;
8616             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8617             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8618             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8619             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8620             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8621             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8622             MakeMove(fromX, fromY, toX, toY, promoChar);
8623             ShowMove(fromX, fromY, toX, toY);
8624               
8625             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8626                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8627               case MT_NONE:
8628               case MT_CHECK:
8629                 break;
8630                 
8631               case MT_CHECKMATE:
8632               case MT_STAINMATE:
8633                 if (WhiteOnMove(currentMove)) {
8634                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8635                 } else {
8636                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8637                 }
8638                 break;
8639                 
8640               case MT_STALEMATE:
8641                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8642                 break;
8643             }
8644
8645             break;
8646             
8647           case CMAIL_RESIGN:
8648             if (WhiteOnMove(currentMove)) {
8649                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8650             } else {
8651                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8652             }
8653             break;
8654             
8655           case CMAIL_ACCEPT:
8656             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8657             break;
8658               
8659           default:
8660             break;
8661         }
8662     }
8663
8664     return;
8665 }
8666
8667 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8668 int
8669 CmailLoadGame(f, gameNumber, title, useList)
8670      FILE *f;
8671      int gameNumber;
8672      char *title;
8673      int useList;
8674 {
8675     int retVal;
8676
8677     if (gameNumber > nCmailGames) {
8678         DisplayError(_("No more games in this message"), 0);
8679         return FALSE;
8680     }
8681     if (f == lastLoadGameFP) {
8682         int offset = gameNumber - lastLoadGameNumber;
8683         if (offset == 0) {
8684             cmailMsg[0] = NULLCHAR;
8685             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8686                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8687                 nCmailMovesRegistered--;
8688             }
8689             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8690             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8691                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8692             }
8693         } else {
8694             if (! RegisterMove()) return FALSE;
8695         }
8696     }
8697
8698     retVal = LoadGame(f, gameNumber, title, useList);
8699
8700     /* Make move registered during previous look at this game, if any */
8701     MakeRegisteredMove();
8702
8703     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8704         commentList[currentMove]
8705           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8706         DisplayComment(currentMove - 1, commentList[currentMove]);
8707     }
8708
8709     return retVal;
8710 }
8711
8712 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8713 int
8714 ReloadGame(offset)
8715      int offset;
8716 {
8717     int gameNumber = lastLoadGameNumber + offset;
8718     if (lastLoadGameFP == NULL) {
8719         DisplayError(_("No game has been loaded yet"), 0);
8720         return FALSE;
8721     }
8722     if (gameNumber <= 0) {
8723         DisplayError(_("Can't back up any further"), 0);
8724         return FALSE;
8725     }
8726     if (cmailMsgLoaded) {
8727         return CmailLoadGame(lastLoadGameFP, gameNumber,
8728                              lastLoadGameTitle, lastLoadGameUseList);
8729     } else {
8730         return LoadGame(lastLoadGameFP, gameNumber,
8731                         lastLoadGameTitle, lastLoadGameUseList);
8732     }
8733 }
8734
8735
8736
8737 /* Load the nth game from open file f */
8738 int
8739 LoadGame(f, gameNumber, title, useList)
8740      FILE *f;
8741      int gameNumber;
8742      char *title;
8743      int useList;
8744 {
8745     ChessMove cm;
8746     char buf[MSG_SIZ];
8747     int gn = gameNumber;
8748     ListGame *lg = NULL;
8749     int numPGNTags = 0;
8750     int err;
8751     GameMode oldGameMode;
8752     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8753
8754     if (appData.debugMode) 
8755         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8756
8757     if (gameMode == Training )
8758         SetTrainingModeOff();
8759
8760     oldGameMode = gameMode;
8761     if (gameMode != BeginningOfGame) {
8762       Reset(FALSE, TRUE);
8763     }
8764
8765     gameFileFP = f;
8766     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8767         fclose(lastLoadGameFP);
8768     }
8769
8770     if (useList) {
8771         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8772         
8773         if (lg) {
8774             fseek(f, lg->offset, 0);
8775             GameListHighlight(gameNumber);
8776             gn = 1;
8777         }
8778         else {
8779             DisplayError(_("Game number out of range"), 0);
8780             return FALSE;
8781         }
8782     } else {
8783         GameListDestroy();
8784         if (fseek(f, 0, 0) == -1) {
8785             if (f == lastLoadGameFP ?
8786                 gameNumber == lastLoadGameNumber + 1 :
8787                 gameNumber == 1) {
8788                 gn = 1;
8789             } else {
8790                 DisplayError(_("Can't seek on game file"), 0);
8791                 return FALSE;
8792             }
8793         }
8794     }
8795     lastLoadGameFP = f;
8796     lastLoadGameNumber = gameNumber;
8797     strcpy(lastLoadGameTitle, title);
8798     lastLoadGameUseList = useList;
8799
8800     yynewfile(f);
8801
8802     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8803       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8804                 lg->gameInfo.black);
8805             DisplayTitle(buf);
8806     } else if (*title != NULLCHAR) {
8807         if (gameNumber > 1) {
8808             sprintf(buf, "%s %d", title, gameNumber);
8809             DisplayTitle(buf);
8810         } else {
8811             DisplayTitle(title);
8812         }
8813     }
8814
8815     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8816         gameMode = PlayFromGameFile;
8817         ModeHighlight();
8818     }
8819
8820     currentMove = forwardMostMove = backwardMostMove = 0;
8821     CopyBoard(boards[0], initialPosition);
8822     StopClocks();
8823
8824     /*
8825      * Skip the first gn-1 games in the file.
8826      * Also skip over anything that precedes an identifiable 
8827      * start of game marker, to avoid being confused by 
8828      * garbage at the start of the file.  Currently 
8829      * recognized start of game markers are the move number "1",
8830      * the pattern "gnuchess .* game", the pattern
8831      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8832      * A game that starts with one of the latter two patterns
8833      * will also have a move number 1, possibly
8834      * following a position diagram.
8835      * 5-4-02: Let's try being more lenient and allowing a game to
8836      * start with an unnumbered move.  Does that break anything?
8837      */
8838     cm = lastLoadGameStart = (ChessMove) 0;
8839     while (gn > 0) {
8840         yyboardindex = forwardMostMove;
8841         cm = (ChessMove) yylex();
8842         switch (cm) {
8843           case (ChessMove) 0:
8844             if (cmailMsgLoaded) {
8845                 nCmailGames = CMAIL_MAX_GAMES - gn;
8846             } else {
8847                 Reset(TRUE, TRUE);
8848                 DisplayError(_("Game not found in file"), 0);
8849             }
8850             return FALSE;
8851
8852           case GNUChessGame:
8853           case XBoardGame:
8854             gn--;
8855             lastLoadGameStart = cm;
8856             break;
8857             
8858           case MoveNumberOne:
8859             switch (lastLoadGameStart) {
8860               case GNUChessGame:
8861               case XBoardGame:
8862               case PGNTag:
8863                 break;
8864               case MoveNumberOne:
8865               case (ChessMove) 0:
8866                 gn--;           /* count this game */
8867                 lastLoadGameStart = cm;
8868                 break;
8869               default:
8870                 /* impossible */
8871                 break;
8872             }
8873             break;
8874
8875           case PGNTag:
8876             switch (lastLoadGameStart) {
8877               case GNUChessGame:
8878               case PGNTag:
8879               case MoveNumberOne:
8880               case (ChessMove) 0:
8881                 gn--;           /* count this game */
8882                 lastLoadGameStart = cm;
8883                 break;
8884               case XBoardGame:
8885                 lastLoadGameStart = cm; /* game counted already */
8886                 break;
8887               default:
8888                 /* impossible */
8889                 break;
8890             }
8891             if (gn > 0) {
8892                 do {
8893                     yyboardindex = forwardMostMove;
8894                     cm = (ChessMove) yylex();
8895                 } while (cm == PGNTag || cm == Comment);
8896             }
8897             break;
8898
8899           case WhiteWins:
8900           case BlackWins:
8901           case GameIsDrawn:
8902             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8903                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8904                     != CMAIL_OLD_RESULT) {
8905                     nCmailResults ++ ;
8906                     cmailResult[  CMAIL_MAX_GAMES
8907                                 - gn - 1] = CMAIL_OLD_RESULT;
8908                 }
8909             }
8910             break;
8911
8912           case NormalMove:
8913             /* Only a NormalMove can be at the start of a game
8914              * without a position diagram. */
8915             if (lastLoadGameStart == (ChessMove) 0) {
8916               gn--;
8917               lastLoadGameStart = MoveNumberOne;
8918             }
8919             break;
8920
8921           default:
8922             break;
8923         }
8924     }
8925     
8926     if (appData.debugMode)
8927       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8928
8929     if (cm == XBoardGame) {
8930         /* Skip any header junk before position diagram and/or move 1 */
8931         for (;;) {
8932             yyboardindex = forwardMostMove;
8933             cm = (ChessMove) yylex();
8934
8935             if (cm == (ChessMove) 0 ||
8936                 cm == GNUChessGame || cm == XBoardGame) {
8937                 /* Empty game; pretend end-of-file and handle later */
8938                 cm = (ChessMove) 0;
8939                 break;
8940             }
8941
8942             if (cm == MoveNumberOne || cm == PositionDiagram ||
8943                 cm == PGNTag || cm == Comment)
8944               break;
8945         }
8946     } else if (cm == GNUChessGame) {
8947         if (gameInfo.event != NULL) {
8948             free(gameInfo.event);
8949         }
8950         gameInfo.event = StrSave(yy_text);
8951     }   
8952
8953     startedFromSetupPosition = FALSE;
8954     while (cm == PGNTag) {
8955         if (appData.debugMode) 
8956           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8957         err = ParsePGNTag(yy_text, &gameInfo);
8958         if (!err) numPGNTags++;
8959
8960         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8961         if(gameInfo.variant != oldVariant) {
8962             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8963             InitPosition(TRUE);
8964             oldVariant = gameInfo.variant;
8965             if (appData.debugMode) 
8966               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8967         }
8968
8969
8970         if (gameInfo.fen != NULL) {
8971           Board initial_position;
8972           startedFromSetupPosition = TRUE;
8973           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8974             Reset(TRUE, TRUE);
8975             DisplayError(_("Bad FEN position in file"), 0);
8976             return FALSE;
8977           }
8978           CopyBoard(boards[0], initial_position);
8979           if (blackPlaysFirst) {
8980             currentMove = forwardMostMove = backwardMostMove = 1;
8981             CopyBoard(boards[1], initial_position);
8982             strcpy(moveList[0], "");
8983             strcpy(parseList[0], "");
8984             timeRemaining[0][1] = whiteTimeRemaining;
8985             timeRemaining[1][1] = blackTimeRemaining;
8986             if (commentList[0] != NULL) {
8987               commentList[1] = commentList[0];
8988               commentList[0] = NULL;
8989             }
8990           } else {
8991             currentMove = forwardMostMove = backwardMostMove = 0;
8992           }
8993           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8994           {   int i;
8995               initialRulePlies = FENrulePlies;
8996               epStatus[forwardMostMove] = FENepStatus;
8997               for( i=0; i< nrCastlingRights; i++ )
8998                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8999           }
9000           yyboardindex = forwardMostMove;
9001           free(gameInfo.fen);
9002           gameInfo.fen = NULL;
9003         }
9004
9005         yyboardindex = forwardMostMove;
9006         cm = (ChessMove) yylex();
9007
9008         /* Handle comments interspersed among the tags */
9009         while (cm == Comment) {
9010             char *p;
9011             if (appData.debugMode) 
9012               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9013             p = yy_text;
9014             if (*p == '{' || *p == '[' || *p == '(') {
9015                 p[strlen(p) - 1] = NULLCHAR;
9016                 p++;
9017             }
9018             while (*p == '\n') p++;
9019             AppendComment(currentMove, p);
9020             yyboardindex = forwardMostMove;
9021             cm = (ChessMove) yylex();
9022         }
9023     }
9024
9025     /* don't rely on existence of Event tag since if game was
9026      * pasted from clipboard the Event tag may not exist
9027      */
9028     if (numPGNTags > 0){
9029         char *tags;
9030         if (gameInfo.variant == VariantNormal) {
9031           gameInfo.variant = StringToVariant(gameInfo.event);
9032         }
9033         if (!matchMode) {
9034           if( appData.autoDisplayTags ) {
9035             tags = PGNTags(&gameInfo);
9036             TagsPopUp(tags, CmailMsg());
9037             free(tags);
9038           }
9039         }
9040     } else {
9041         /* Make something up, but don't display it now */
9042         SetGameInfo();
9043         TagsPopDown();
9044     }
9045
9046     if (cm == PositionDiagram) {
9047         int i, j;
9048         char *p;
9049         Board initial_position;
9050
9051         if (appData.debugMode)
9052           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9053
9054         if (!startedFromSetupPosition) {
9055             p = yy_text;
9056             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9057               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9058                 switch (*p) {
9059                   case '[':
9060                   case '-':
9061                   case ' ':
9062                   case '\t':
9063                   case '\n':
9064                   case '\r':
9065                     break;
9066                   default:
9067                     initial_position[i][j++] = CharToPiece(*p);
9068                     break;
9069                 }
9070             while (*p == ' ' || *p == '\t' ||
9071                    *p == '\n' || *p == '\r') p++;
9072         
9073             if (strncmp(p, "black", strlen("black"))==0)
9074               blackPlaysFirst = TRUE;
9075             else
9076               blackPlaysFirst = FALSE;
9077             startedFromSetupPosition = TRUE;
9078         
9079             CopyBoard(boards[0], initial_position);
9080             if (blackPlaysFirst) {
9081                 currentMove = forwardMostMove = backwardMostMove = 1;
9082                 CopyBoard(boards[1], initial_position);
9083                 strcpy(moveList[0], "");
9084                 strcpy(parseList[0], "");
9085                 timeRemaining[0][1] = whiteTimeRemaining;
9086                 timeRemaining[1][1] = blackTimeRemaining;
9087                 if (commentList[0] != NULL) {
9088                     commentList[1] = commentList[0];
9089                     commentList[0] = NULL;
9090                 }
9091             } else {
9092                 currentMove = forwardMostMove = backwardMostMove = 0;
9093             }
9094         }
9095         yyboardindex = forwardMostMove;
9096         cm = (ChessMove) yylex();
9097     }
9098
9099     if (first.pr == NoProc) {
9100         StartChessProgram(&first);
9101     }
9102     InitChessProgram(&first, FALSE);
9103     SendToProgram("force\n", &first);
9104     if (startedFromSetupPosition) {
9105         SendBoard(&first, forwardMostMove);
9106     if (appData.debugMode) {
9107         fprintf(debugFP, "Load Game\n");
9108     }
9109         DisplayBothClocks();
9110     }      
9111
9112     /* [HGM] server: flag to write setup moves in broadcast file as one */
9113     loadFlag = appData.suppressLoadMoves;
9114
9115     while (cm == Comment) {
9116         char *p;
9117         if (appData.debugMode) 
9118           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9119         p = yy_text;
9120         if (*p == '{' || *p == '[' || *p == '(') {
9121             p[strlen(p) - 1] = NULLCHAR;
9122             p++;
9123         }
9124         while (*p == '\n') p++;
9125         AppendComment(currentMove, p);
9126         yyboardindex = forwardMostMove;
9127         cm = (ChessMove) yylex();
9128     }
9129
9130     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9131         cm == WhiteWins || cm == BlackWins ||
9132         cm == GameIsDrawn || cm == GameUnfinished) {
9133         DisplayMessage("", _("No moves in game"));
9134         if (cmailMsgLoaded) {
9135             if (appData.debugMode)
9136               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9137             ClearHighlights();
9138             flipView = FALSE;
9139         }
9140         DrawPosition(FALSE, boards[currentMove]);
9141         DisplayBothClocks();
9142         gameMode = EditGame;
9143         ModeHighlight();
9144         gameFileFP = NULL;
9145         cmailOldMove = 0;
9146         return TRUE;
9147     }
9148
9149     // [HGM] PV info: routine tests if comment empty
9150     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9151         DisplayComment(currentMove - 1, commentList[currentMove]);
9152     }
9153     if (!matchMode && appData.timeDelay != 0) 
9154       DrawPosition(FALSE, boards[currentMove]);
9155
9156     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9157       programStats.ok_to_send = 1;
9158     }
9159
9160     /* if the first token after the PGN tags is a move
9161      * and not move number 1, retrieve it from the parser 
9162      */
9163     if (cm != MoveNumberOne)
9164         LoadGameOneMove(cm);
9165
9166     /* load the remaining moves from the file */
9167     while (LoadGameOneMove((ChessMove)0)) {
9168       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9169       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9170     }
9171
9172     /* rewind to the start of the game */
9173     currentMove = backwardMostMove;
9174
9175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9176
9177     if (oldGameMode == AnalyzeFile ||
9178         oldGameMode == AnalyzeMode) {
9179       AnalyzeFileEvent();
9180     }
9181
9182     if (matchMode || appData.timeDelay == 0) {
9183       ToEndEvent();
9184       gameMode = EditGame;
9185       ModeHighlight();
9186     } else if (appData.timeDelay > 0) {
9187       AutoPlayGameLoop();
9188     }
9189
9190     if (appData.debugMode) 
9191         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9192
9193     loadFlag = 0; /* [HGM] true game starts */
9194     return TRUE;
9195 }
9196
9197 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9198 int
9199 ReloadPosition(offset)
9200      int offset;
9201 {
9202     int positionNumber = lastLoadPositionNumber + offset;
9203     if (lastLoadPositionFP == NULL) {
9204         DisplayError(_("No position has been loaded yet"), 0);
9205         return FALSE;
9206     }
9207     if (positionNumber <= 0) {
9208         DisplayError(_("Can't back up any further"), 0);
9209         return FALSE;
9210     }
9211     return LoadPosition(lastLoadPositionFP, positionNumber,
9212                         lastLoadPositionTitle);
9213 }
9214
9215 /* Load the nth position from the given file */
9216 int
9217 LoadPositionFromFile(filename, n, title)
9218      char *filename;
9219      int n;
9220      char *title;
9221 {
9222     FILE *f;
9223     char buf[MSG_SIZ];
9224
9225     if (strcmp(filename, "-") == 0) {
9226         return LoadPosition(stdin, n, "stdin");
9227     } else {
9228         f = fopen(filename, "rb");
9229         if (f == NULL) {
9230             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9231             DisplayError(buf, errno);
9232             return FALSE;
9233         } else {
9234             return LoadPosition(f, n, title);
9235         }
9236     }
9237 }
9238
9239 /* Load the nth position from the given open file, and close it */
9240 int
9241 LoadPosition(f, positionNumber, title)
9242      FILE *f;
9243      int positionNumber;
9244      char *title;
9245 {
9246     char *p, line[MSG_SIZ];
9247     Board initial_position;
9248     int i, j, fenMode, pn;
9249     
9250     if (gameMode == Training )
9251         SetTrainingModeOff();
9252
9253     if (gameMode != BeginningOfGame) {
9254         Reset(FALSE, TRUE);
9255     }
9256     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9257         fclose(lastLoadPositionFP);
9258     }
9259     if (positionNumber == 0) positionNumber = 1;
9260     lastLoadPositionFP = f;
9261     lastLoadPositionNumber = positionNumber;
9262     strcpy(lastLoadPositionTitle, title);
9263     if (first.pr == NoProc) {
9264       StartChessProgram(&first);
9265       InitChessProgram(&first, FALSE);
9266     }    
9267     pn = positionNumber;
9268     if (positionNumber < 0) {
9269         /* Negative position number means to seek to that byte offset */
9270         if (fseek(f, -positionNumber, 0) == -1) {
9271             DisplayError(_("Can't seek on position file"), 0);
9272             return FALSE;
9273         };
9274         pn = 1;
9275     } else {
9276         if (fseek(f, 0, 0) == -1) {
9277             if (f == lastLoadPositionFP ?
9278                 positionNumber == lastLoadPositionNumber + 1 :
9279                 positionNumber == 1) {
9280                 pn = 1;
9281             } else {
9282                 DisplayError(_("Can't seek on position file"), 0);
9283                 return FALSE;
9284             }
9285         }
9286     }
9287     /* See if this file is FEN or old-style xboard */
9288     if (fgets(line, MSG_SIZ, f) == NULL) {
9289         DisplayError(_("Position not found in file"), 0);
9290         return FALSE;
9291     }
9292     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9293     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9294
9295     if (pn >= 2) {
9296         if (fenMode || line[0] == '#') pn--;
9297         while (pn > 0) {
9298             /* skip positions before number pn */
9299             if (fgets(line, MSG_SIZ, f) == NULL) {
9300                 Reset(TRUE, TRUE);
9301                 DisplayError(_("Position not found in file"), 0);
9302                 return FALSE;
9303             }
9304             if (fenMode || line[0] == '#') pn--;
9305         }
9306     }
9307
9308     if (fenMode) {
9309         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9310             DisplayError(_("Bad FEN position in file"), 0);
9311             return FALSE;
9312         }
9313     } else {
9314         (void) fgets(line, MSG_SIZ, f);
9315         (void) fgets(line, MSG_SIZ, f);
9316     
9317         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9318             (void) fgets(line, MSG_SIZ, f);
9319             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9320                 if (*p == ' ')
9321                   continue;
9322                 initial_position[i][j++] = CharToPiece(*p);
9323             }
9324         }
9325     
9326         blackPlaysFirst = FALSE;
9327         if (!feof(f)) {
9328             (void) fgets(line, MSG_SIZ, f);
9329             if (strncmp(line, "black", strlen("black"))==0)
9330               blackPlaysFirst = TRUE;
9331         }
9332     }
9333     startedFromSetupPosition = TRUE;
9334     
9335     SendToProgram("force\n", &first);
9336     CopyBoard(boards[0], initial_position);
9337     if (blackPlaysFirst) {
9338         currentMove = forwardMostMove = backwardMostMove = 1;
9339         strcpy(moveList[0], "");
9340         strcpy(parseList[0], "");
9341         CopyBoard(boards[1], initial_position);
9342         DisplayMessage("", _("Black to play"));
9343     } else {
9344         currentMove = forwardMostMove = backwardMostMove = 0;
9345         DisplayMessage("", _("White to play"));
9346     }
9347           /* [HGM] copy FEN attributes as well */
9348           {   int i;
9349               initialRulePlies = FENrulePlies;
9350               epStatus[forwardMostMove] = FENepStatus;
9351               for( i=0; i< nrCastlingRights; i++ )
9352                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9353           }
9354     SendBoard(&first, forwardMostMove);
9355     if (appData.debugMode) {
9356 int i, j;
9357   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9358   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9359         fprintf(debugFP, "Load Position\n");
9360     }
9361
9362     if (positionNumber > 1) {
9363         sprintf(line, "%s %d", title, positionNumber);
9364         DisplayTitle(line);
9365     } else {
9366         DisplayTitle(title);
9367     }
9368     gameMode = EditGame;
9369     ModeHighlight();
9370     ResetClocks();
9371     timeRemaining[0][1] = whiteTimeRemaining;
9372     timeRemaining[1][1] = blackTimeRemaining;
9373     DrawPosition(FALSE, boards[currentMove]);
9374    
9375     return TRUE;
9376 }
9377
9378
9379 void
9380 CopyPlayerNameIntoFileName(dest, src)
9381      char **dest, *src;
9382 {
9383     while (*src != NULLCHAR && *src != ',') {
9384         if (*src == ' ') {
9385             *(*dest)++ = '_';
9386             src++;
9387         } else {
9388             *(*dest)++ = *src++;
9389         }
9390     }
9391 }
9392
9393 char *DefaultFileName(ext)
9394      char *ext;
9395 {
9396     static char def[MSG_SIZ];
9397     char *p;
9398
9399     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9400         p = def;
9401         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9402         *p++ = '-';
9403         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9404         *p++ = '.';
9405         strcpy(p, ext);
9406     } else {
9407         def[0] = NULLCHAR;
9408     }
9409     return def;
9410 }
9411
9412 /* Save the current game to the given file */
9413 int
9414 SaveGameToFile(filename, append)
9415      char *filename;
9416      int append;
9417 {
9418     FILE *f;
9419     char buf[MSG_SIZ];
9420
9421     if (strcmp(filename, "-") == 0) {
9422         return SaveGame(stdout, 0, NULL);
9423     } else {
9424         f = fopen(filename, append ? "a" : "w");
9425         if (f == NULL) {
9426             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9427             DisplayError(buf, errno);
9428             return FALSE;
9429         } else {
9430             return SaveGame(f, 0, NULL);
9431         }
9432     }
9433 }
9434
9435 char *
9436 SavePart(str)
9437      char *str;
9438 {
9439     static char buf[MSG_SIZ];
9440     char *p;
9441     
9442     p = strchr(str, ' ');
9443     if (p == NULL) return str;
9444     strncpy(buf, str, p - str);
9445     buf[p - str] = NULLCHAR;
9446     return buf;
9447 }
9448
9449 #define PGN_MAX_LINE 75
9450
9451 #define PGN_SIDE_WHITE  0
9452 #define PGN_SIDE_BLACK  1
9453
9454 /* [AS] */
9455 static int FindFirstMoveOutOfBook( int side )
9456 {
9457     int result = -1;
9458
9459     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9460         int index = backwardMostMove;
9461         int has_book_hit = 0;
9462
9463         if( (index % 2) != side ) {
9464             index++;
9465         }
9466
9467         while( index < forwardMostMove ) {
9468             /* Check to see if engine is in book */
9469             int depth = pvInfoList[index].depth;
9470             int score = pvInfoList[index].score;
9471             int in_book = 0;
9472
9473             if( depth <= 2 ) {
9474                 in_book = 1;
9475             }
9476             else if( score == 0 && depth == 63 ) {
9477                 in_book = 1; /* Zappa */
9478             }
9479             else if( score == 2 && depth == 99 ) {
9480                 in_book = 1; /* Abrok */
9481             }
9482
9483             has_book_hit += in_book;
9484
9485             if( ! in_book ) {
9486                 result = index;
9487
9488                 break;
9489             }
9490
9491             index += 2;
9492         }
9493     }
9494
9495     return result;
9496 }
9497
9498 /* [AS] */
9499 void GetOutOfBookInfo( char * buf )
9500 {
9501     int oob[2];
9502     int i;
9503     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9504
9505     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9506     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9507
9508     *buf = '\0';
9509
9510     if( oob[0] >= 0 || oob[1] >= 0 ) {
9511         for( i=0; i<2; i++ ) {
9512             int idx = oob[i];
9513
9514             if( idx >= 0 ) {
9515                 if( i > 0 && oob[0] >= 0 ) {
9516                     strcat( buf, "   " );
9517                 }
9518
9519                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9520                 sprintf( buf+strlen(buf), "%s%.2f", 
9521                     pvInfoList[idx].score >= 0 ? "+" : "",
9522                     pvInfoList[idx].score / 100.0 );
9523             }
9524         }
9525     }
9526 }
9527
9528 /* Save game in PGN style and close the file */
9529 int
9530 SaveGamePGN(f)
9531      FILE *f;
9532 {
9533     int i, offset, linelen, newblock;
9534     time_t tm;
9535 //    char *movetext;
9536     char numtext[32];
9537     int movelen, numlen, blank;
9538     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9539
9540     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9541     
9542     tm = time((time_t *) NULL);
9543     
9544     PrintPGNTags(f, &gameInfo);
9545     
9546     if (backwardMostMove > 0 || startedFromSetupPosition) {
9547         char *fen = PositionToFEN(backwardMostMove, NULL);
9548         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9549         fprintf(f, "\n{--------------\n");
9550         PrintPosition(f, backwardMostMove);
9551         fprintf(f, "--------------}\n");
9552         free(fen);
9553     }
9554     else {
9555         /* [AS] Out of book annotation */
9556         if( appData.saveOutOfBookInfo ) {
9557             char buf[64];
9558
9559             GetOutOfBookInfo( buf );
9560
9561             if( buf[0] != '\0' ) {
9562                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9563             }
9564         }
9565
9566         fprintf(f, "\n");
9567     }
9568
9569     i = backwardMostMove;
9570     linelen = 0;
9571     newblock = TRUE;
9572
9573     while (i < forwardMostMove) {
9574         /* Print comments preceding this move */
9575         if (commentList[i] != NULL) {
9576             if (linelen > 0) fprintf(f, "\n");
9577             fprintf(f, "{\n%s}\n", commentList[i]);
9578             linelen = 0;
9579             newblock = TRUE;
9580         }
9581
9582         /* Format move number */
9583         if ((i % 2) == 0) {
9584             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9585         } else {
9586             if (newblock) {
9587                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9588             } else {
9589                 numtext[0] = NULLCHAR;
9590             }
9591         }
9592         numlen = strlen(numtext);
9593         newblock = FALSE;
9594
9595         /* Print move number */
9596         blank = linelen > 0 && numlen > 0;
9597         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9598             fprintf(f, "\n");
9599             linelen = 0;
9600             blank = 0;
9601         }
9602         if (blank) {
9603             fprintf(f, " ");
9604             linelen++;
9605         }
9606         fprintf(f, "%s", numtext);
9607         linelen += numlen;
9608
9609         /* Get move */
9610         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9611         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9612
9613         /* Print move */
9614         blank = linelen > 0 && movelen > 0;
9615         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9616             fprintf(f, "\n");
9617             linelen = 0;
9618             blank = 0;
9619         }
9620         if (blank) {
9621             fprintf(f, " ");
9622             linelen++;
9623         }
9624         fprintf(f, "%s", move_buffer);
9625         linelen += movelen;
9626
9627         /* [AS] Add PV info if present */
9628         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9629             /* [HGM] add time */
9630             char buf[MSG_SIZ]; int seconds = 0;
9631
9632             if(i >= backwardMostMove) {
9633                 if(WhiteOnMove(i))
9634                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9635                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9636                 else
9637                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9638                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9639             }
9640             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9641
9642             if( seconds <= 0) buf[0] = 0; else
9643             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9644                 seconds = (seconds + 4)/10; // round to full seconds
9645                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9646                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9647             }
9648
9649             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9650                 pvInfoList[i].score >= 0 ? "+" : "",
9651                 pvInfoList[i].score / 100.0,
9652                 pvInfoList[i].depth,
9653                 buf );
9654
9655             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9656
9657             /* Print score/depth */
9658             blank = linelen > 0 && movelen > 0;
9659             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9660                 fprintf(f, "\n");
9661                 linelen = 0;
9662                 blank = 0;
9663             }
9664             if (blank) {
9665                 fprintf(f, " ");
9666                 linelen++;
9667             }
9668             fprintf(f, "%s", move_buffer);
9669             linelen += movelen;
9670         }
9671
9672         i++;
9673     }
9674     
9675     /* Start a new line */
9676     if (linelen > 0) fprintf(f, "\n");
9677
9678     /* Print comments after last move */
9679     if (commentList[i] != NULL) {
9680         fprintf(f, "{\n%s}\n", commentList[i]);
9681     }
9682
9683     /* Print result */
9684     if (gameInfo.resultDetails != NULL &&
9685         gameInfo.resultDetails[0] != NULLCHAR) {
9686         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9687                 PGNResult(gameInfo.result));
9688     } else {
9689         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9690     }
9691
9692     fclose(f);
9693     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9694     return TRUE;
9695 }
9696
9697 /* Save game in old style and close the file */
9698 int
9699 SaveGameOldStyle(f)
9700      FILE *f;
9701 {
9702     int i, offset;
9703     time_t tm;
9704     
9705     tm = time((time_t *) NULL);
9706     
9707     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9708     PrintOpponents(f);
9709     
9710     if (backwardMostMove > 0 || startedFromSetupPosition) {
9711         fprintf(f, "\n[--------------\n");
9712         PrintPosition(f, backwardMostMove);
9713         fprintf(f, "--------------]\n");
9714     } else {
9715         fprintf(f, "\n");
9716     }
9717
9718     i = backwardMostMove;
9719     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9720
9721     while (i < forwardMostMove) {
9722         if (commentList[i] != NULL) {
9723             fprintf(f, "[%s]\n", commentList[i]);
9724         }
9725
9726         if ((i % 2) == 1) {
9727             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9728             i++;
9729         } else {
9730             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9731             i++;
9732             if (commentList[i] != NULL) {
9733                 fprintf(f, "\n");
9734                 continue;
9735             }
9736             if (i >= forwardMostMove) {
9737                 fprintf(f, "\n");
9738                 break;
9739             }
9740             fprintf(f, "%s\n", parseList[i]);
9741             i++;
9742         }
9743     }
9744     
9745     if (commentList[i] != NULL) {
9746         fprintf(f, "[%s]\n", commentList[i]);
9747     }
9748
9749     /* This isn't really the old style, but it's close enough */
9750     if (gameInfo.resultDetails != NULL &&
9751         gameInfo.resultDetails[0] != NULLCHAR) {
9752         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9753                 gameInfo.resultDetails);
9754     } else {
9755         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9756     }
9757
9758     fclose(f);
9759     return TRUE;
9760 }
9761
9762 /* Save the current game to open file f and close the file */
9763 int
9764 SaveGame(f, dummy, dummy2)
9765      FILE *f;
9766      int dummy;
9767      char *dummy2;
9768 {
9769     if (gameMode == EditPosition) EditPositionDone();
9770     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9771     if (appData.oldSaveStyle)
9772       return SaveGameOldStyle(f);
9773     else
9774       return SaveGamePGN(f);
9775 }
9776
9777 /* Save the current position to the given file */
9778 int
9779 SavePositionToFile(filename)
9780      char *filename;
9781 {
9782     FILE *f;
9783     char buf[MSG_SIZ];
9784
9785     if (strcmp(filename, "-") == 0) {
9786         return SavePosition(stdout, 0, NULL);
9787     } else {
9788         f = fopen(filename, "a");
9789         if (f == NULL) {
9790             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9791             DisplayError(buf, errno);
9792             return FALSE;
9793         } else {
9794             SavePosition(f, 0, NULL);
9795             return TRUE;
9796         }
9797     }
9798 }
9799
9800 /* Save the current position to the given open file and close the file */
9801 int
9802 SavePosition(f, dummy, dummy2)
9803      FILE *f;
9804      int dummy;
9805      char *dummy2;
9806 {
9807     time_t tm;
9808     char *fen;
9809     
9810     if (appData.oldSaveStyle) {
9811         tm = time((time_t *) NULL);
9812     
9813         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9814         PrintOpponents(f);
9815         fprintf(f, "[--------------\n");
9816         PrintPosition(f, currentMove);
9817         fprintf(f, "--------------]\n");
9818     } else {
9819         fen = PositionToFEN(currentMove, NULL);
9820         fprintf(f, "%s\n", fen);
9821         free(fen);
9822     }
9823     fclose(f);
9824     return TRUE;
9825 }
9826
9827 void
9828 ReloadCmailMsgEvent(unregister)
9829      int unregister;
9830 {
9831 #if !WIN32
9832     static char *inFilename = NULL;
9833     static char *outFilename;
9834     int i;
9835     struct stat inbuf, outbuf;
9836     int status;
9837     
9838     /* Any registered moves are unregistered if unregister is set, */
9839     /* i.e. invoked by the signal handler */
9840     if (unregister) {
9841         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9842             cmailMoveRegistered[i] = FALSE;
9843             if (cmailCommentList[i] != NULL) {
9844                 free(cmailCommentList[i]);
9845                 cmailCommentList[i] = NULL;
9846             }
9847         }
9848         nCmailMovesRegistered = 0;
9849     }
9850
9851     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9852         cmailResult[i] = CMAIL_NOT_RESULT;
9853     }
9854     nCmailResults = 0;
9855
9856     if (inFilename == NULL) {
9857         /* Because the filenames are static they only get malloced once  */
9858         /* and they never get freed                                      */
9859         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9860         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9861
9862         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9863         sprintf(outFilename, "%s.out", appData.cmailGameName);
9864     }
9865     
9866     status = stat(outFilename, &outbuf);
9867     if (status < 0) {
9868         cmailMailedMove = FALSE;
9869     } else {
9870         status = stat(inFilename, &inbuf);
9871         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9872     }
9873     
9874     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9875        counts the games, notes how each one terminated, etc.
9876        
9877        It would be nice to remove this kludge and instead gather all
9878        the information while building the game list.  (And to keep it
9879        in the game list nodes instead of having a bunch of fixed-size
9880        parallel arrays.)  Note this will require getting each game's
9881        termination from the PGN tags, as the game list builder does
9882        not process the game moves.  --mann
9883        */
9884     cmailMsgLoaded = TRUE;
9885     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9886     
9887     /* Load first game in the file or popup game menu */
9888     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9889
9890 #endif /* !WIN32 */
9891     return;
9892 }
9893
9894 int
9895 RegisterMove()
9896 {
9897     FILE *f;
9898     char string[MSG_SIZ];
9899
9900     if (   cmailMailedMove
9901         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9902         return TRUE;            /* Allow free viewing  */
9903     }
9904
9905     /* Unregister move to ensure that we don't leave RegisterMove        */
9906     /* with the move registered when the conditions for registering no   */
9907     /* longer hold                                                       */
9908     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9909         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9910         nCmailMovesRegistered --;
9911
9912         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9913           {
9914               free(cmailCommentList[lastLoadGameNumber - 1]);
9915               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9916           }
9917     }
9918
9919     if (cmailOldMove == -1) {
9920         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9921         return FALSE;
9922     }
9923
9924     if (currentMove > cmailOldMove + 1) {
9925         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9926         return FALSE;
9927     }
9928
9929     if (currentMove < cmailOldMove) {
9930         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9931         return FALSE;
9932     }
9933
9934     if (forwardMostMove > currentMove) {
9935         /* Silently truncate extra moves */
9936         TruncateGame();
9937     }
9938
9939     if (   (currentMove == cmailOldMove + 1)
9940         || (   (currentMove == cmailOldMove)
9941             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9942                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9943         if (gameInfo.result != GameUnfinished) {
9944             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9945         }
9946
9947         if (commentList[currentMove] != NULL) {
9948             cmailCommentList[lastLoadGameNumber - 1]
9949               = StrSave(commentList[currentMove]);
9950         }
9951         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9952
9953         if (appData.debugMode)
9954           fprintf(debugFP, "Saving %s for game %d\n",
9955                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9956
9957         sprintf(string,
9958                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9959         
9960         f = fopen(string, "w");
9961         if (appData.oldSaveStyle) {
9962             SaveGameOldStyle(f); /* also closes the file */
9963             
9964             sprintf(string, "%s.pos.out", appData.cmailGameName);
9965             f = fopen(string, "w");
9966             SavePosition(f, 0, NULL); /* also closes the file */
9967         } else {
9968             fprintf(f, "{--------------\n");
9969             PrintPosition(f, currentMove);
9970             fprintf(f, "--------------}\n\n");
9971             
9972             SaveGame(f, 0, NULL); /* also closes the file*/
9973         }
9974         
9975         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9976         nCmailMovesRegistered ++;
9977     } else if (nCmailGames == 1) {
9978         DisplayError(_("You have not made a move yet"), 0);
9979         return FALSE;
9980     }
9981
9982     return TRUE;
9983 }
9984
9985 void
9986 MailMoveEvent()
9987 {
9988 #if !WIN32
9989     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9990     FILE *commandOutput;
9991     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9992     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9993     int nBuffers;
9994     int i;
9995     int archived;
9996     char *arcDir;
9997
9998     if (! cmailMsgLoaded) {
9999         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10000         return;
10001     }
10002
10003     if (nCmailGames == nCmailResults) {
10004         DisplayError(_("No unfinished games"), 0);
10005         return;
10006     }
10007
10008 #if CMAIL_PROHIBIT_REMAIL
10009     if (cmailMailedMove) {
10010         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);
10011         DisplayError(msg, 0);
10012         return;
10013     }
10014 #endif
10015
10016     if (! (cmailMailedMove || RegisterMove())) return;
10017     
10018     if (   cmailMailedMove
10019         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10020         sprintf(string, partCommandString,
10021                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10022         commandOutput = popen(string, "r");
10023
10024         if (commandOutput == NULL) {
10025             DisplayError(_("Failed to invoke cmail"), 0);
10026         } else {
10027             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10028                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10029             }
10030             if (nBuffers > 1) {
10031                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10032                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10033                 nBytes = MSG_SIZ - 1;
10034             } else {
10035                 (void) memcpy(msg, buffer, nBytes);
10036             }
10037             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10038
10039             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10040                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10041
10042                 archived = TRUE;
10043                 for (i = 0; i < nCmailGames; i ++) {
10044                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10045                         archived = FALSE;
10046                     }
10047                 }
10048                 if (   archived
10049                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10050                         != NULL)) {
10051                     sprintf(buffer, "%s/%s.%s.archive",
10052                             arcDir,
10053                             appData.cmailGameName,
10054                             gameInfo.date);
10055                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10056                     cmailMsgLoaded = FALSE;
10057                 }
10058             }
10059
10060             DisplayInformation(msg);
10061             pclose(commandOutput);
10062         }
10063     } else {
10064         if ((*cmailMsg) != '\0') {
10065             DisplayInformation(cmailMsg);
10066         }
10067     }
10068
10069     return;
10070 #endif /* !WIN32 */
10071 }
10072
10073 char *
10074 CmailMsg()
10075 {
10076 #if WIN32
10077     return NULL;
10078 #else
10079     int  prependComma = 0;
10080     char number[5];
10081     char string[MSG_SIZ];       /* Space for game-list */
10082     int  i;
10083     
10084     if (!cmailMsgLoaded) return "";
10085
10086     if (cmailMailedMove) {
10087         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10088     } else {
10089         /* Create a list of games left */
10090         sprintf(string, "[");
10091         for (i = 0; i < nCmailGames; i ++) {
10092             if (! (   cmailMoveRegistered[i]
10093                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10094                 if (prependComma) {
10095                     sprintf(number, ",%d", i + 1);
10096                 } else {
10097                     sprintf(number, "%d", i + 1);
10098                     prependComma = 1;
10099                 }
10100                 
10101                 strcat(string, number);
10102             }
10103         }
10104         strcat(string, "]");
10105
10106         if (nCmailMovesRegistered + nCmailResults == 0) {
10107             switch (nCmailGames) {
10108               case 1:
10109                 sprintf(cmailMsg,
10110                         _("Still need to make move for game\n"));
10111                 break;
10112                 
10113               case 2:
10114                 sprintf(cmailMsg,
10115                         _("Still need to make moves for both games\n"));
10116                 break;
10117                 
10118               default:
10119                 sprintf(cmailMsg,
10120                         _("Still need to make moves for all %d games\n"),
10121                         nCmailGames);
10122                 break;
10123             }
10124         } else {
10125             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10126               case 1:
10127                 sprintf(cmailMsg,
10128                         _("Still need to make a move for game %s\n"),
10129                         string);
10130                 break;
10131                 
10132               case 0:
10133                 if (nCmailResults == nCmailGames) {
10134                     sprintf(cmailMsg, _("No unfinished games\n"));
10135                 } else {
10136                     sprintf(cmailMsg, _("Ready to send mail\n"));
10137                 }
10138                 break;
10139                 
10140               default:
10141                 sprintf(cmailMsg,
10142                         _("Still need to make moves for games %s\n"),
10143                         string);
10144             }
10145         }
10146     }
10147     return cmailMsg;
10148 #endif /* WIN32 */
10149 }
10150
10151 void
10152 ResetGameEvent()
10153 {
10154     if (gameMode == Training)
10155       SetTrainingModeOff();
10156
10157     Reset(TRUE, TRUE);
10158     cmailMsgLoaded = FALSE;
10159     if (appData.icsActive) {
10160       SendToICS(ics_prefix);
10161       SendToICS("refresh\n");
10162     }
10163 }
10164
10165 void
10166 ExitEvent(status)
10167      int status;
10168 {
10169     exiting++;
10170     if (exiting > 2) {
10171       /* Give up on clean exit */
10172       exit(status);
10173     }
10174     if (exiting > 1) {
10175       /* Keep trying for clean exit */
10176       return;
10177     }
10178
10179     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10180
10181     if (telnetISR != NULL) {
10182       RemoveInputSource(telnetISR);
10183     }
10184     if (icsPR != NoProc) {
10185       DestroyChildProcess(icsPR, TRUE);
10186     }
10187
10188     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10189     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10190
10191     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10192     /* make sure this other one finishes before killing it!                  */
10193     if(endingGame) { int count = 0;
10194         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10195         while(endingGame && count++ < 10) DoSleep(1);
10196         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10197     }
10198
10199     /* Kill off chess programs */
10200     if (first.pr != NoProc) {
10201         ExitAnalyzeMode();
10202         
10203         DoSleep( appData.delayBeforeQuit );
10204         SendToProgram("quit\n", &first);
10205         DoSleep( appData.delayAfterQuit );
10206         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10207     }
10208     if (second.pr != NoProc) {
10209         DoSleep( appData.delayBeforeQuit );
10210         SendToProgram("quit\n", &second);
10211         DoSleep( appData.delayAfterQuit );
10212         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10213     }
10214     if (first.isr != NULL) {
10215         RemoveInputSource(first.isr);
10216     }
10217     if (second.isr != NULL) {
10218         RemoveInputSource(second.isr);
10219     }
10220
10221     ShutDownFrontEnd();
10222     exit(status);
10223 }
10224
10225 void
10226 PauseEvent()
10227 {
10228     if (appData.debugMode)
10229         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10230     if (pausing) {
10231         pausing = FALSE;
10232         ModeHighlight();
10233         if (gameMode == MachinePlaysWhite ||
10234             gameMode == MachinePlaysBlack) {
10235             StartClocks();
10236         } else {
10237             DisplayBothClocks();
10238         }
10239         if (gameMode == PlayFromGameFile) {
10240             if (appData.timeDelay >= 0) 
10241                 AutoPlayGameLoop();
10242         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10243             Reset(FALSE, TRUE);
10244             SendToICS(ics_prefix);
10245             SendToICS("refresh\n");
10246         } else if (currentMove < forwardMostMove) {
10247             ForwardInner(forwardMostMove);
10248         }
10249         pauseExamInvalid = FALSE;
10250     } else {
10251         switch (gameMode) {
10252           default:
10253             return;
10254           case IcsExamining:
10255             pauseExamForwardMostMove = forwardMostMove;
10256             pauseExamInvalid = FALSE;
10257             /* fall through */
10258           case IcsObserving:
10259           case IcsPlayingWhite:
10260           case IcsPlayingBlack:
10261             pausing = TRUE;
10262             ModeHighlight();
10263             return;
10264           case PlayFromGameFile:
10265             (void) StopLoadGameTimer();
10266             pausing = TRUE;
10267             ModeHighlight();
10268             break;
10269           case BeginningOfGame:
10270             if (appData.icsActive) return;
10271             /* else fall through */
10272           case MachinePlaysWhite:
10273           case MachinePlaysBlack:
10274           case TwoMachinesPlay:
10275             if (forwardMostMove == 0)
10276               return;           /* don't pause if no one has moved */
10277             if ((gameMode == MachinePlaysWhite &&
10278                  !WhiteOnMove(forwardMostMove)) ||
10279                 (gameMode == MachinePlaysBlack &&
10280                  WhiteOnMove(forwardMostMove))) {
10281                 StopClocks();
10282             }
10283             pausing = TRUE;
10284             ModeHighlight();
10285             break;
10286         }
10287     }
10288 }
10289
10290 void
10291 EditCommentEvent()
10292 {
10293     char title[MSG_SIZ];
10294
10295     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10296         strcpy(title, _("Edit comment"));
10297     } else {
10298         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10299                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10300                 parseList[currentMove - 1]);
10301     }
10302
10303     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10304 }
10305
10306
10307 void
10308 EditTagsEvent()
10309 {
10310     char *tags = PGNTags(&gameInfo);
10311     EditTagsPopUp(tags);
10312     free(tags);
10313 }
10314
10315 void
10316 AnalyzeModeEvent()
10317 {
10318     if (appData.noChessProgram || gameMode == AnalyzeMode)
10319       return;
10320
10321     if (gameMode != AnalyzeFile) {
10322         if (!appData.icsEngineAnalyze) {
10323                EditGameEvent();
10324                if (gameMode != EditGame) return;
10325         }
10326         ResurrectChessProgram();
10327         SendToProgram("analyze\n", &first);
10328         first.analyzing = TRUE;
10329         /*first.maybeThinking = TRUE;*/
10330         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10331         AnalysisPopUp(_("Analysis"),
10332                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10333     }
10334     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10335     pausing = FALSE;
10336     ModeHighlight();
10337     SetGameInfo();
10338
10339     StartAnalysisClock();
10340     GetTimeMark(&lastNodeCountTime);
10341     lastNodeCount = 0;
10342 }
10343
10344 void
10345 AnalyzeFileEvent()
10346 {
10347     if (appData.noChessProgram || gameMode == AnalyzeFile)
10348       return;
10349
10350     if (gameMode != AnalyzeMode) {
10351         EditGameEvent();
10352         if (gameMode != EditGame) return;
10353         ResurrectChessProgram();
10354         SendToProgram("analyze\n", &first);
10355         first.analyzing = TRUE;
10356         /*first.maybeThinking = TRUE;*/
10357         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10358         AnalysisPopUp(_("Analysis"),
10359                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10360     }
10361     gameMode = AnalyzeFile;
10362     pausing = FALSE;
10363     ModeHighlight();
10364     SetGameInfo();
10365
10366     StartAnalysisClock();
10367     GetTimeMark(&lastNodeCountTime);
10368     lastNodeCount = 0;
10369 }
10370
10371 void
10372 MachineWhiteEvent()
10373 {
10374     char buf[MSG_SIZ];
10375     char *bookHit = NULL;
10376
10377     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10378       return;
10379
10380
10381     if (gameMode == PlayFromGameFile || 
10382         gameMode == TwoMachinesPlay  || 
10383         gameMode == Training         || 
10384         gameMode == AnalyzeMode      || 
10385         gameMode == EndOfGame)
10386         EditGameEvent();
10387
10388     if (gameMode == EditPosition) 
10389         EditPositionDone();
10390
10391     if (!WhiteOnMove(currentMove)) {
10392         DisplayError(_("It is not White's turn"), 0);
10393         return;
10394     }
10395   
10396     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10397       ExitAnalyzeMode();
10398
10399     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10400         gameMode == AnalyzeFile)
10401         TruncateGame();
10402
10403     ResurrectChessProgram();    /* in case it isn't running */
10404     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10405         gameMode = MachinePlaysWhite;
10406         ResetClocks();
10407     } else
10408     gameMode = MachinePlaysWhite;
10409     pausing = FALSE;
10410     ModeHighlight();
10411     SetGameInfo();
10412     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10413     DisplayTitle(buf);
10414     if (first.sendName) {
10415       sprintf(buf, "name %s\n", gameInfo.black);
10416       SendToProgram(buf, &first);
10417     }
10418     if (first.sendTime) {
10419       if (first.useColors) {
10420         SendToProgram("black\n", &first); /*gnu kludge*/
10421       }
10422       SendTimeRemaining(&first, TRUE);
10423     }
10424     if (first.useColors) {
10425       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10426     }
10427     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10428     SetMachineThinkingEnables();
10429     first.maybeThinking = TRUE;
10430     StartClocks();
10431     firstMove = FALSE;
10432
10433     if (appData.autoFlipView && !flipView) {
10434       flipView = !flipView;
10435       DrawPosition(FALSE, NULL);
10436       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10437     }
10438
10439     if(bookHit) { // [HGM] book: simulate book reply
10440         static char bookMove[MSG_SIZ]; // a bit generous?
10441
10442         programStats.nodes = programStats.depth = programStats.time = 
10443         programStats.score = programStats.got_only_move = 0;
10444         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10445
10446         strcpy(bookMove, "move ");
10447         strcat(bookMove, bookHit);
10448         HandleMachineMove(bookMove, &first);
10449     }
10450 }
10451
10452 void
10453 MachineBlackEvent()
10454 {
10455     char buf[MSG_SIZ];
10456    char *bookHit = NULL;
10457
10458     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10459         return;
10460
10461
10462     if (gameMode == PlayFromGameFile || 
10463         gameMode == TwoMachinesPlay  || 
10464         gameMode == Training         || 
10465         gameMode == AnalyzeMode      || 
10466         gameMode == EndOfGame)
10467         EditGameEvent();
10468
10469     if (gameMode == EditPosition) 
10470         EditPositionDone();
10471
10472     if (WhiteOnMove(currentMove)) {
10473         DisplayError(_("It is not Black's turn"), 0);
10474         return;
10475     }
10476     
10477     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10478       ExitAnalyzeMode();
10479
10480     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10481         gameMode == AnalyzeFile)
10482         TruncateGame();
10483
10484     ResurrectChessProgram();    /* in case it isn't running */
10485     gameMode = MachinePlaysBlack;
10486     pausing = FALSE;
10487     ModeHighlight();
10488     SetGameInfo();
10489     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10490     DisplayTitle(buf);
10491     if (first.sendName) {
10492       sprintf(buf, "name %s\n", gameInfo.white);
10493       SendToProgram(buf, &first);
10494     }
10495     if (first.sendTime) {
10496       if (first.useColors) {
10497         SendToProgram("white\n", &first); /*gnu kludge*/
10498       }
10499       SendTimeRemaining(&first, FALSE);
10500     }
10501     if (first.useColors) {
10502       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10503     }
10504     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10505     SetMachineThinkingEnables();
10506     first.maybeThinking = TRUE;
10507     StartClocks();
10508
10509     if (appData.autoFlipView && flipView) {
10510       flipView = !flipView;
10511       DrawPosition(FALSE, NULL);
10512       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10513     }
10514     if(bookHit) { // [HGM] book: simulate book reply
10515         static char bookMove[MSG_SIZ]; // a bit generous?
10516
10517         programStats.nodes = programStats.depth = programStats.time = 
10518         programStats.score = programStats.got_only_move = 0;
10519         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10520
10521         strcpy(bookMove, "move ");
10522         strcat(bookMove, bookHit);
10523         HandleMachineMove(bookMove, &first);
10524     }
10525 }
10526
10527
10528 void
10529 DisplayTwoMachinesTitle()
10530 {
10531     char buf[MSG_SIZ];
10532     if (appData.matchGames > 0) {
10533         if (first.twoMachinesColor[0] == 'w') {
10534             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10535                     gameInfo.white, gameInfo.black,
10536                     first.matchWins, second.matchWins,
10537                     matchGame - 1 - (first.matchWins + second.matchWins));
10538         } else {
10539             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10540                     gameInfo.white, gameInfo.black,
10541                     second.matchWins, first.matchWins,
10542                     matchGame - 1 - (first.matchWins + second.matchWins));
10543         }
10544     } else {
10545         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10546     }
10547     DisplayTitle(buf);
10548 }
10549
10550 void
10551 TwoMachinesEvent P((void))
10552 {
10553     int i;
10554     char buf[MSG_SIZ];
10555     ChessProgramState *onmove;
10556     char *bookHit = NULL;
10557     
10558     if (appData.noChessProgram) return;
10559
10560     switch (gameMode) {
10561       case TwoMachinesPlay:
10562         return;
10563       case MachinePlaysWhite:
10564       case MachinePlaysBlack:
10565         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10566             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10567             return;
10568         }
10569         /* fall through */
10570       case BeginningOfGame:
10571       case PlayFromGameFile:
10572       case EndOfGame:
10573         EditGameEvent();
10574         if (gameMode != EditGame) return;
10575         break;
10576       case EditPosition:
10577         EditPositionDone();
10578         break;
10579       case AnalyzeMode:
10580       case AnalyzeFile:
10581         ExitAnalyzeMode();
10582         break;
10583       case EditGame:
10584       default:
10585         break;
10586     }
10587
10588     forwardMostMove = currentMove;
10589     ResurrectChessProgram();    /* in case first program isn't running */
10590
10591     if (second.pr == NULL) {
10592         StartChessProgram(&second);
10593         if (second.protocolVersion == 1) {
10594           TwoMachinesEventIfReady();
10595         } else {
10596           /* kludge: allow timeout for initial "feature" command */
10597           FreezeUI();
10598           DisplayMessage("", _("Starting second chess program"));
10599           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10600         }
10601         return;
10602     }
10603     DisplayMessage("", "");
10604     InitChessProgram(&second, FALSE);
10605     SendToProgram("force\n", &second);
10606     if (startedFromSetupPosition) {
10607         SendBoard(&second, backwardMostMove);
10608     if (appData.debugMode) {
10609         fprintf(debugFP, "Two Machines\n");
10610     }
10611     }
10612     for (i = backwardMostMove; i < forwardMostMove; i++) {
10613         SendMoveToProgram(i, &second);
10614     }
10615
10616     gameMode = TwoMachinesPlay;
10617     pausing = FALSE;
10618     ModeHighlight();
10619     SetGameInfo();
10620     DisplayTwoMachinesTitle();
10621     firstMove = TRUE;
10622     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10623         onmove = &first;
10624     } else {
10625         onmove = &second;
10626     }
10627
10628     SendToProgram(first.computerString, &first);
10629     if (first.sendName) {
10630       sprintf(buf, "name %s\n", second.tidy);
10631       SendToProgram(buf, &first);
10632     }
10633     SendToProgram(second.computerString, &second);
10634     if (second.sendName) {
10635       sprintf(buf, "name %s\n", first.tidy);
10636       SendToProgram(buf, &second);
10637     }
10638
10639     ResetClocks();
10640     if (!first.sendTime || !second.sendTime) {
10641         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10642         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10643     }
10644     if (onmove->sendTime) {
10645       if (onmove->useColors) {
10646         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10647       }
10648       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10649     }
10650     if (onmove->useColors) {
10651       SendToProgram(onmove->twoMachinesColor, onmove);
10652     }
10653     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10654 //    SendToProgram("go\n", onmove);
10655     onmove->maybeThinking = TRUE;
10656     SetMachineThinkingEnables();
10657
10658     StartClocks();
10659
10660     if(bookHit) { // [HGM] book: simulate book reply
10661         static char bookMove[MSG_SIZ]; // a bit generous?
10662
10663         programStats.nodes = programStats.depth = programStats.time = 
10664         programStats.score = programStats.got_only_move = 0;
10665         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10666
10667         strcpy(bookMove, "move ");
10668         strcat(bookMove, bookHit);
10669         HandleMachineMove(bookMove, &first);
10670     }
10671 }
10672
10673 void
10674 TrainingEvent()
10675 {
10676     if (gameMode == Training) {
10677       SetTrainingModeOff();
10678       gameMode = PlayFromGameFile;
10679       DisplayMessage("", _("Training mode off"));
10680     } else {
10681       gameMode = Training;
10682       animateTraining = appData.animate;
10683
10684       /* make sure we are not already at the end of the game */
10685       if (currentMove < forwardMostMove) {
10686         SetTrainingModeOn();
10687         DisplayMessage("", _("Training mode on"));
10688       } else {
10689         gameMode = PlayFromGameFile;
10690         DisplayError(_("Already at end of game"), 0);
10691       }
10692     }
10693     ModeHighlight();
10694 }
10695
10696 void
10697 IcsClientEvent()
10698 {
10699     if (!appData.icsActive) return;
10700     switch (gameMode) {
10701       case IcsPlayingWhite:
10702       case IcsPlayingBlack:
10703       case IcsObserving:
10704       case IcsIdle:
10705       case BeginningOfGame:
10706       case IcsExamining:
10707         return;
10708
10709       case EditGame:
10710         break;
10711
10712       case EditPosition:
10713         EditPositionDone();
10714         break;
10715
10716       case AnalyzeMode:
10717       case AnalyzeFile:
10718         ExitAnalyzeMode();
10719         break;
10720         
10721       default:
10722         EditGameEvent();
10723         break;
10724     }
10725
10726     gameMode = IcsIdle;
10727     ModeHighlight();
10728     return;
10729 }
10730
10731
10732 void
10733 EditGameEvent()
10734 {
10735     int i;
10736
10737     switch (gameMode) {
10738       case Training:
10739         SetTrainingModeOff();
10740         break;
10741       case MachinePlaysWhite:
10742       case MachinePlaysBlack:
10743       case BeginningOfGame:
10744         SendToProgram("force\n", &first);
10745         SetUserThinkingEnables();
10746         break;
10747       case PlayFromGameFile:
10748         (void) StopLoadGameTimer();
10749         if (gameFileFP != NULL) {
10750             gameFileFP = NULL;
10751         }
10752         break;
10753       case EditPosition:
10754         EditPositionDone();
10755         break;
10756       case AnalyzeMode:
10757       case AnalyzeFile:
10758         ExitAnalyzeMode();
10759         SendToProgram("force\n", &first);
10760         break;
10761       case TwoMachinesPlay:
10762         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10763         ResurrectChessProgram();
10764         SetUserThinkingEnables();
10765         break;
10766       case EndOfGame:
10767         ResurrectChessProgram();
10768         break;
10769       case IcsPlayingBlack:
10770       case IcsPlayingWhite:
10771         DisplayError(_("Warning: You are still playing a game"), 0);
10772         break;
10773       case IcsObserving:
10774         DisplayError(_("Warning: You are still observing a game"), 0);
10775         break;
10776       case IcsExamining:
10777         DisplayError(_("Warning: You are still examining a game"), 0);
10778         break;
10779       case IcsIdle:
10780         break;
10781       case EditGame:
10782       default:
10783         return;
10784     }
10785     
10786     pausing = FALSE;
10787     StopClocks();
10788     first.offeredDraw = second.offeredDraw = 0;
10789
10790     if (gameMode == PlayFromGameFile) {
10791         whiteTimeRemaining = timeRemaining[0][currentMove];
10792         blackTimeRemaining = timeRemaining[1][currentMove];
10793         DisplayTitle("");
10794     }
10795
10796     if (gameMode == MachinePlaysWhite ||
10797         gameMode == MachinePlaysBlack ||
10798         gameMode == TwoMachinesPlay ||
10799         gameMode == EndOfGame) {
10800         i = forwardMostMove;
10801         while (i > currentMove) {
10802             SendToProgram("undo\n", &first);
10803             i--;
10804         }
10805         whiteTimeRemaining = timeRemaining[0][currentMove];
10806         blackTimeRemaining = timeRemaining[1][currentMove];
10807         DisplayBothClocks();
10808         if (whiteFlag || blackFlag) {
10809             whiteFlag = blackFlag = 0;
10810         }
10811         DisplayTitle("");
10812     }           
10813     
10814     gameMode = EditGame;
10815     ModeHighlight();
10816     SetGameInfo();
10817 }
10818
10819
10820 void
10821 EditPositionEvent()
10822 {
10823     if (gameMode == EditPosition) {
10824         EditGameEvent();
10825         return;
10826     }
10827     
10828     EditGameEvent();
10829     if (gameMode != EditGame) return;
10830     
10831     gameMode = EditPosition;
10832     ModeHighlight();
10833     SetGameInfo();
10834     if (currentMove > 0)
10835       CopyBoard(boards[0], boards[currentMove]);
10836     
10837     blackPlaysFirst = !WhiteOnMove(currentMove);
10838     ResetClocks();
10839     currentMove = forwardMostMove = backwardMostMove = 0;
10840     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10841     DisplayMove(-1);
10842 }
10843
10844 void
10845 ExitAnalyzeMode()
10846 {
10847     /* [DM] icsEngineAnalyze - possible call from other functions */
10848     if (appData.icsEngineAnalyze) {
10849         appData.icsEngineAnalyze = FALSE;
10850
10851         DisplayMessage("",_("Close ICS engine analyze..."));
10852     }
10853     if (first.analysisSupport && first.analyzing) {
10854       SendToProgram("exit\n", &first);
10855       first.analyzing = FALSE;
10856     }
10857     AnalysisPopDown();
10858     thinkOutput[0] = NULLCHAR;
10859 }
10860
10861 void
10862 EditPositionDone()
10863 {
10864     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10865
10866     startedFromSetupPosition = TRUE;
10867     InitChessProgram(&first, FALSE);
10868     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10869     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10870         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10871         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10872     } else castlingRights[0][2] = -1;
10873     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10874         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10875         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10876     } else castlingRights[0][5] = -1;
10877     SendToProgram("force\n", &first);
10878     if (blackPlaysFirst) {
10879         strcpy(moveList[0], "");
10880         strcpy(parseList[0], "");
10881         currentMove = forwardMostMove = backwardMostMove = 1;
10882         CopyBoard(boards[1], boards[0]);
10883         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10884         { int i;
10885           epStatus[1] = epStatus[0];
10886           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10887         }
10888     } else {
10889         currentMove = forwardMostMove = backwardMostMove = 0;
10890     }
10891     SendBoard(&first, forwardMostMove);
10892     if (appData.debugMode) {
10893         fprintf(debugFP, "EditPosDone\n");
10894     }
10895     DisplayTitle("");
10896     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10897     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10898     gameMode = EditGame;
10899     ModeHighlight();
10900     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10901     ClearHighlights(); /* [AS] */
10902 }
10903
10904 /* Pause for `ms' milliseconds */
10905 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10906 void
10907 TimeDelay(ms)
10908      long ms;
10909 {
10910     TimeMark m1, m2;
10911
10912     GetTimeMark(&m1);
10913     do {
10914         GetTimeMark(&m2);
10915     } while (SubtractTimeMarks(&m2, &m1) < ms);
10916 }
10917
10918 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10919 void
10920 SendMultiLineToICS(buf)
10921      char *buf;
10922 {
10923     char temp[MSG_SIZ+1], *p;
10924     int len;
10925
10926     len = strlen(buf);
10927     if (len > MSG_SIZ)
10928       len = MSG_SIZ;
10929   
10930     strncpy(temp, buf, len);
10931     temp[len] = 0;
10932
10933     p = temp;
10934     while (*p) {
10935         if (*p == '\n' || *p == '\r')
10936           *p = ' ';
10937         ++p;
10938     }
10939
10940     strcat(temp, "\n");
10941     SendToICS(temp);
10942     SendToPlayer(temp, strlen(temp));
10943 }
10944
10945 void
10946 SetWhiteToPlayEvent()
10947 {
10948     if (gameMode == EditPosition) {
10949         blackPlaysFirst = FALSE;
10950         DisplayBothClocks();    /* works because currentMove is 0 */
10951     } else if (gameMode == IcsExamining) {
10952         SendToICS(ics_prefix);
10953         SendToICS("tomove white\n");
10954     }
10955 }
10956
10957 void
10958 SetBlackToPlayEvent()
10959 {
10960     if (gameMode == EditPosition) {
10961         blackPlaysFirst = TRUE;
10962         currentMove = 1;        /* kludge */
10963         DisplayBothClocks();
10964         currentMove = 0;
10965     } else if (gameMode == IcsExamining) {
10966         SendToICS(ics_prefix);
10967         SendToICS("tomove black\n");
10968     }
10969 }
10970
10971 void
10972 EditPositionMenuEvent(selection, x, y)
10973      ChessSquare selection;
10974      int x, y;
10975 {
10976     char buf[MSG_SIZ];
10977     ChessSquare piece = boards[0][y][x];
10978
10979     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10980
10981     switch (selection) {
10982       case ClearBoard:
10983         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10984             SendToICS(ics_prefix);
10985             SendToICS("bsetup clear\n");
10986         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10987             SendToICS(ics_prefix);
10988             SendToICS("clearboard\n");
10989         } else {
10990             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10991                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10992                 for (y = 0; y < BOARD_HEIGHT; y++) {
10993                     if (gameMode == IcsExamining) {
10994                         if (boards[currentMove][y][x] != EmptySquare) {
10995                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10996                                     AAA + x, ONE + y);
10997                             SendToICS(buf);
10998                         }
10999                     } else {
11000                         boards[0][y][x] = p;
11001                     }
11002                 }
11003             }
11004         }
11005         if (gameMode == EditPosition) {
11006             DrawPosition(FALSE, boards[0]);
11007         }
11008         break;
11009
11010       case WhitePlay:
11011         SetWhiteToPlayEvent();
11012         break;
11013
11014       case BlackPlay:
11015         SetBlackToPlayEvent();
11016         break;
11017
11018       case EmptySquare:
11019         if (gameMode == IcsExamining) {
11020             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11021             SendToICS(buf);
11022         } else {
11023             boards[0][y][x] = EmptySquare;
11024             DrawPosition(FALSE, boards[0]);
11025         }
11026         break;
11027
11028       case PromotePiece:
11029         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11030            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11031             selection = (ChessSquare) (PROMOTED piece);
11032         } else if(piece == EmptySquare) selection = WhiteSilver;
11033         else selection = (ChessSquare)((int)piece - 1);
11034         goto defaultlabel;
11035
11036       case DemotePiece:
11037         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11038            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11039             selection = (ChessSquare) (DEMOTED piece);
11040         } else if(piece == EmptySquare) selection = BlackSilver;
11041         else selection = (ChessSquare)((int)piece + 1);       
11042         goto defaultlabel;
11043
11044       case WhiteQueen:
11045       case BlackQueen:
11046         if(gameInfo.variant == VariantShatranj ||
11047            gameInfo.variant == VariantXiangqi  ||
11048            gameInfo.variant == VariantCourier    )
11049             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11050         goto defaultlabel;
11051
11052       case WhiteKing:
11053       case BlackKing:
11054         if(gameInfo.variant == VariantXiangqi)
11055             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11056         if(gameInfo.variant == VariantKnightmate)
11057             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11058       default:
11059         defaultlabel:
11060         if (gameMode == IcsExamining) {
11061             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11062                     PieceToChar(selection), AAA + x, ONE + y);
11063             SendToICS(buf);
11064         } else {
11065             boards[0][y][x] = selection;
11066             DrawPosition(FALSE, boards[0]);
11067         }
11068         break;
11069     }
11070 }
11071
11072
11073 void
11074 DropMenuEvent(selection, x, y)
11075      ChessSquare selection;
11076      int x, y;
11077 {
11078     ChessMove moveType;
11079
11080     switch (gameMode) {
11081       case IcsPlayingWhite:
11082       case MachinePlaysBlack:
11083         if (!WhiteOnMove(currentMove)) {
11084             DisplayMoveError(_("It is Black's turn"));
11085             return;
11086         }
11087         moveType = WhiteDrop;
11088         break;
11089       case IcsPlayingBlack:
11090       case MachinePlaysWhite:
11091         if (WhiteOnMove(currentMove)) {
11092             DisplayMoveError(_("It is White's turn"));
11093             return;
11094         }
11095         moveType = BlackDrop;
11096         break;
11097       case EditGame:
11098         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11099         break;
11100       default:
11101         return;
11102     }
11103
11104     if (moveType == BlackDrop && selection < BlackPawn) {
11105       selection = (ChessSquare) ((int) selection
11106                                  + (int) BlackPawn - (int) WhitePawn);
11107     }
11108     if (boards[currentMove][y][x] != EmptySquare) {
11109         DisplayMoveError(_("That square is occupied"));
11110         return;
11111     }
11112
11113     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11114 }
11115
11116 void
11117 AcceptEvent()
11118 {
11119     /* Accept a pending offer of any kind from opponent */
11120     
11121     if (appData.icsActive) {
11122         SendToICS(ics_prefix);
11123         SendToICS("accept\n");
11124     } else if (cmailMsgLoaded) {
11125         if (currentMove == cmailOldMove &&
11126             commentList[cmailOldMove] != NULL &&
11127             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11128                    "Black offers a draw" : "White offers a draw")) {
11129             TruncateGame();
11130             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11132         } else {
11133             DisplayError(_("There is no pending offer on this move"), 0);
11134             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11135         }
11136     } else {
11137         /* Not used for offers from chess program */
11138     }
11139 }
11140
11141 void
11142 DeclineEvent()
11143 {
11144     /* Decline a pending offer of any kind from opponent */
11145     
11146     if (appData.icsActive) {
11147         SendToICS(ics_prefix);
11148         SendToICS("decline\n");
11149     } else if (cmailMsgLoaded) {
11150         if (currentMove == cmailOldMove &&
11151             commentList[cmailOldMove] != NULL &&
11152             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11153                    "Black offers a draw" : "White offers a draw")) {
11154 #ifdef NOTDEF
11155             AppendComment(cmailOldMove, "Draw declined");
11156             DisplayComment(cmailOldMove - 1, "Draw declined");
11157 #endif /*NOTDEF*/
11158         } else {
11159             DisplayError(_("There is no pending offer on this move"), 0);
11160         }
11161     } else {
11162         /* Not used for offers from chess program */
11163     }
11164 }
11165
11166 void
11167 RematchEvent()
11168 {
11169     /* Issue ICS rematch command */
11170     if (appData.icsActive) {
11171         SendToICS(ics_prefix);
11172         SendToICS("rematch\n");
11173     }
11174 }
11175
11176 void
11177 CallFlagEvent()
11178 {
11179     /* Call your opponent's flag (claim a win on time) */
11180     if (appData.icsActive) {
11181         SendToICS(ics_prefix);
11182         SendToICS("flag\n");
11183     } else {
11184         switch (gameMode) {
11185           default:
11186             return;
11187           case MachinePlaysWhite:
11188             if (whiteFlag) {
11189                 if (blackFlag)
11190                   GameEnds(GameIsDrawn, "Both players ran out of time",
11191                            GE_PLAYER);
11192                 else
11193                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11194             } else {
11195                 DisplayError(_("Your opponent is not out of time"), 0);
11196             }
11197             break;
11198           case MachinePlaysBlack:
11199             if (blackFlag) {
11200                 if (whiteFlag)
11201                   GameEnds(GameIsDrawn, "Both players ran out of time",
11202                            GE_PLAYER);
11203                 else
11204                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11205             } else {
11206                 DisplayError(_("Your opponent is not out of time"), 0);
11207             }
11208             break;
11209         }
11210     }
11211 }
11212
11213 void
11214 DrawEvent()
11215 {
11216     /* Offer draw or accept pending draw offer from opponent */
11217     
11218     if (appData.icsActive) {
11219         /* Note: tournament rules require draw offers to be
11220            made after you make your move but before you punch
11221            your clock.  Currently ICS doesn't let you do that;
11222            instead, you immediately punch your clock after making
11223            a move, but you can offer a draw at any time. */
11224         
11225         SendToICS(ics_prefix);
11226         SendToICS("draw\n");
11227     } else if (cmailMsgLoaded) {
11228         if (currentMove == cmailOldMove &&
11229             commentList[cmailOldMove] != NULL &&
11230             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11231                    "Black offers a draw" : "White offers a draw")) {
11232             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11233             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11234         } else if (currentMove == cmailOldMove + 1) {
11235             char *offer = WhiteOnMove(cmailOldMove) ?
11236               "White offers a draw" : "Black offers a draw";
11237             AppendComment(currentMove, offer);
11238             DisplayComment(currentMove - 1, offer);
11239             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11240         } else {
11241             DisplayError(_("You must make your move before offering a draw"), 0);
11242             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11243         }
11244     } else if (first.offeredDraw) {
11245         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11246     } else {
11247         if (first.sendDrawOffers) {
11248             SendToProgram("draw\n", &first);
11249             userOfferedDraw = TRUE;
11250         }
11251     }
11252 }
11253
11254 void
11255 AdjournEvent()
11256 {
11257     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11258     
11259     if (appData.icsActive) {
11260         SendToICS(ics_prefix);
11261         SendToICS("adjourn\n");
11262     } else {
11263         /* Currently GNU Chess doesn't offer or accept Adjourns */
11264     }
11265 }
11266
11267
11268 void
11269 AbortEvent()
11270 {
11271     /* Offer Abort or accept pending Abort offer from opponent */
11272     
11273     if (appData.icsActive) {
11274         SendToICS(ics_prefix);
11275         SendToICS("abort\n");
11276     } else {
11277         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11278     }
11279 }
11280
11281 void
11282 ResignEvent()
11283 {
11284     /* Resign.  You can do this even if it's not your turn. */
11285     
11286     if (appData.icsActive) {
11287         SendToICS(ics_prefix);
11288         SendToICS("resign\n");
11289     } else {
11290         switch (gameMode) {
11291           case MachinePlaysWhite:
11292             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11293             break;
11294           case MachinePlaysBlack:
11295             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11296             break;
11297           case EditGame:
11298             if (cmailMsgLoaded) {
11299                 TruncateGame();
11300                 if (WhiteOnMove(cmailOldMove)) {
11301                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11302                 } else {
11303                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11304                 }
11305                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11306             }
11307             break;
11308           default:
11309             break;
11310         }
11311     }
11312 }
11313
11314
11315 void
11316 StopObservingEvent()
11317 {
11318     /* Stop observing current games */
11319     SendToICS(ics_prefix);
11320     SendToICS("unobserve\n");
11321 }
11322
11323 void
11324 StopExaminingEvent()
11325 {
11326     /* Stop observing current game */
11327     SendToICS(ics_prefix);
11328     SendToICS("unexamine\n");
11329 }
11330
11331 void
11332 ForwardInner(target)
11333      int target;
11334 {
11335     int limit;
11336
11337     if (appData.debugMode)
11338         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11339                 target, currentMove, forwardMostMove);
11340
11341     if (gameMode == EditPosition)
11342       return;
11343
11344     if (gameMode == PlayFromGameFile && !pausing)
11345       PauseEvent();
11346     
11347     if (gameMode == IcsExamining && pausing)
11348       limit = pauseExamForwardMostMove;
11349     else
11350       limit = forwardMostMove;
11351     
11352     if (target > limit) target = limit;
11353
11354     if (target > 0 && moveList[target - 1][0]) {
11355         int fromX, fromY, toX, toY;
11356         toX = moveList[target - 1][2] - AAA;
11357         toY = moveList[target - 1][3] - ONE;
11358         if (moveList[target - 1][1] == '@') {
11359             if (appData.highlightLastMove) {
11360                 SetHighlights(-1, -1, toX, toY);
11361             }
11362         } else {
11363             fromX = moveList[target - 1][0] - AAA;
11364             fromY = moveList[target - 1][1] - ONE;
11365             if (target == currentMove + 1) {
11366                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11367             }
11368             if (appData.highlightLastMove) {
11369                 SetHighlights(fromX, fromY, toX, toY);
11370             }
11371         }
11372     }
11373     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11374         gameMode == Training || gameMode == PlayFromGameFile || 
11375         gameMode == AnalyzeFile) {
11376         while (currentMove < target) {
11377             SendMoveToProgram(currentMove++, &first);
11378         }
11379     } else {
11380         currentMove = target;
11381     }
11382     
11383     if (gameMode == EditGame || gameMode == EndOfGame) {
11384         whiteTimeRemaining = timeRemaining[0][currentMove];
11385         blackTimeRemaining = timeRemaining[1][currentMove];
11386     }
11387     DisplayBothClocks();
11388     DisplayMove(currentMove - 1);
11389     DrawPosition(FALSE, boards[currentMove]);
11390     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11391     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11392         DisplayComment(currentMove - 1, commentList[currentMove]);
11393     }
11394 }
11395
11396
11397 void
11398 ForwardEvent()
11399 {
11400     if (gameMode == IcsExamining && !pausing) {
11401         SendToICS(ics_prefix);
11402         SendToICS("forward\n");
11403     } else {
11404         ForwardInner(currentMove + 1);
11405     }
11406 }
11407
11408 void
11409 ToEndEvent()
11410 {
11411     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11412         /* to optimze, we temporarily turn off analysis mode while we feed
11413          * the remaining moves to the engine. Otherwise we get analysis output
11414          * after each move.
11415          */ 
11416         if (first.analysisSupport) {
11417           SendToProgram("exit\nforce\n", &first);
11418           first.analyzing = FALSE;
11419         }
11420     }
11421         
11422     if (gameMode == IcsExamining && !pausing) {
11423         SendToICS(ics_prefix);
11424         SendToICS("forward 999999\n");
11425     } else {
11426         ForwardInner(forwardMostMove);
11427     }
11428
11429     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11430         /* we have fed all the moves, so reactivate analysis mode */
11431         SendToProgram("analyze\n", &first);
11432         first.analyzing = TRUE;
11433         /*first.maybeThinking = TRUE;*/
11434         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11435     }
11436 }
11437
11438 void
11439 BackwardInner(target)
11440      int target;
11441 {
11442     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11443
11444     if (appData.debugMode)
11445         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11446                 target, currentMove, forwardMostMove);
11447
11448     if (gameMode == EditPosition) return;
11449     if (currentMove <= backwardMostMove) {
11450         ClearHighlights();
11451         DrawPosition(full_redraw, boards[currentMove]);
11452         return;
11453     }
11454     if (gameMode == PlayFromGameFile && !pausing)
11455       PauseEvent();
11456     
11457     if (moveList[target][0]) {
11458         int fromX, fromY, toX, toY;
11459         toX = moveList[target][2] - AAA;
11460         toY = moveList[target][3] - ONE;
11461         if (moveList[target][1] == '@') {
11462             if (appData.highlightLastMove) {
11463                 SetHighlights(-1, -1, toX, toY);
11464             }
11465         } else {
11466             fromX = moveList[target][0] - AAA;
11467             fromY = moveList[target][1] - ONE;
11468             if (target == currentMove - 1) {
11469                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11470             }
11471             if (appData.highlightLastMove) {
11472                 SetHighlights(fromX, fromY, toX, toY);
11473             }
11474         }
11475     }
11476     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11477         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11478         while (currentMove > target) {
11479             SendToProgram("undo\n", &first);
11480             currentMove--;
11481         }
11482     } else {
11483         currentMove = target;
11484     }
11485     
11486     if (gameMode == EditGame || gameMode == EndOfGame) {
11487         whiteTimeRemaining = timeRemaining[0][currentMove];
11488         blackTimeRemaining = timeRemaining[1][currentMove];
11489     }
11490     DisplayBothClocks();
11491     DisplayMove(currentMove - 1);
11492     DrawPosition(full_redraw, boards[currentMove]);
11493     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11494     // [HGM] PV info: routine tests if comment empty
11495     DisplayComment(currentMove - 1, commentList[currentMove]);
11496 }
11497
11498 void
11499 BackwardEvent()
11500 {
11501     if (gameMode == IcsExamining && !pausing) {
11502         SendToICS(ics_prefix);
11503         SendToICS("backward\n");
11504     } else {
11505         BackwardInner(currentMove - 1);
11506     }
11507 }
11508
11509 void
11510 ToStartEvent()
11511 {
11512     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11513         /* to optimze, we temporarily turn off analysis mode while we undo
11514          * all the moves. Otherwise we get analysis output after each undo.
11515          */ 
11516         if (first.analysisSupport) {
11517           SendToProgram("exit\nforce\n", &first);
11518           first.analyzing = FALSE;
11519         }
11520     }
11521
11522     if (gameMode == IcsExamining && !pausing) {
11523         SendToICS(ics_prefix);
11524         SendToICS("backward 999999\n");
11525     } else {
11526         BackwardInner(backwardMostMove);
11527     }
11528
11529     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11530         /* we have fed all the moves, so reactivate analysis mode */
11531         SendToProgram("analyze\n", &first);
11532         first.analyzing = TRUE;
11533         /*first.maybeThinking = TRUE;*/
11534         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11535     }
11536 }
11537
11538 void
11539 ToNrEvent(int to)
11540 {
11541   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11542   if (to >= forwardMostMove) to = forwardMostMove;
11543   if (to <= backwardMostMove) to = backwardMostMove;
11544   if (to < currentMove) {
11545     BackwardInner(to);
11546   } else {
11547     ForwardInner(to);
11548   }
11549 }
11550
11551 void
11552 RevertEvent()
11553 {
11554     if (gameMode != IcsExamining) {
11555         DisplayError(_("You are not examining a game"), 0);
11556         return;
11557     }
11558     if (pausing) {
11559         DisplayError(_("You can't revert while pausing"), 0);
11560         return;
11561     }
11562     SendToICS(ics_prefix);
11563     SendToICS("revert\n");
11564 }
11565
11566 void
11567 RetractMoveEvent()
11568 {
11569     switch (gameMode) {
11570       case MachinePlaysWhite:
11571       case MachinePlaysBlack:
11572         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11573             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11574             return;
11575         }
11576         if (forwardMostMove < 2) return;
11577         currentMove = forwardMostMove = forwardMostMove - 2;
11578         whiteTimeRemaining = timeRemaining[0][currentMove];
11579         blackTimeRemaining = timeRemaining[1][currentMove];
11580         DisplayBothClocks();
11581         DisplayMove(currentMove - 1);
11582         ClearHighlights();/*!! could figure this out*/
11583         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11584         SendToProgram("remove\n", &first);
11585         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11586         break;
11587
11588       case BeginningOfGame:
11589       default:
11590         break;
11591
11592       case IcsPlayingWhite:
11593       case IcsPlayingBlack:
11594         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11595             SendToICS(ics_prefix);
11596             SendToICS("takeback 2\n");
11597         } else {
11598             SendToICS(ics_prefix);
11599             SendToICS("takeback 1\n");
11600         }
11601         break;
11602     }
11603 }
11604
11605 void
11606 MoveNowEvent()
11607 {
11608     ChessProgramState *cps;
11609
11610     switch (gameMode) {
11611       case MachinePlaysWhite:
11612         if (!WhiteOnMove(forwardMostMove)) {
11613             DisplayError(_("It is your turn"), 0);
11614             return;
11615         }
11616         cps = &first;
11617         break;
11618       case MachinePlaysBlack:
11619         if (WhiteOnMove(forwardMostMove)) {
11620             DisplayError(_("It is your turn"), 0);
11621             return;
11622         }
11623         cps = &first;
11624         break;
11625       case TwoMachinesPlay:
11626         if (WhiteOnMove(forwardMostMove) ==
11627             (first.twoMachinesColor[0] == 'w')) {
11628             cps = &first;
11629         } else {
11630             cps = &second;
11631         }
11632         break;
11633       case BeginningOfGame:
11634       default:
11635         return;
11636     }
11637     SendToProgram("?\n", cps);
11638 }
11639
11640 void
11641 TruncateGameEvent()
11642 {
11643     EditGameEvent();
11644     if (gameMode != EditGame) return;
11645     TruncateGame();
11646 }
11647
11648 void
11649 TruncateGame()
11650 {
11651     if (forwardMostMove > currentMove) {
11652         if (gameInfo.resultDetails != NULL) {
11653             free(gameInfo.resultDetails);
11654             gameInfo.resultDetails = NULL;
11655             gameInfo.result = GameUnfinished;
11656         }
11657         forwardMostMove = currentMove;
11658         HistorySet(parseList, backwardMostMove, forwardMostMove,
11659                    currentMove-1);
11660     }
11661 }
11662
11663 void
11664 HintEvent()
11665 {
11666     if (appData.noChessProgram) return;
11667     switch (gameMode) {
11668       case MachinePlaysWhite:
11669         if (WhiteOnMove(forwardMostMove)) {
11670             DisplayError(_("Wait until your turn"), 0);
11671             return;
11672         }
11673         break;
11674       case BeginningOfGame:
11675       case MachinePlaysBlack:
11676         if (!WhiteOnMove(forwardMostMove)) {
11677             DisplayError(_("Wait until your turn"), 0);
11678             return;
11679         }
11680         break;
11681       default:
11682         DisplayError(_("No hint available"), 0);
11683         return;
11684     }
11685     SendToProgram("hint\n", &first);
11686     hintRequested = TRUE;
11687 }
11688
11689 void
11690 BookEvent()
11691 {
11692     if (appData.noChessProgram) return;
11693     switch (gameMode) {
11694       case MachinePlaysWhite:
11695         if (WhiteOnMove(forwardMostMove)) {
11696             DisplayError(_("Wait until your turn"), 0);
11697             return;
11698         }
11699         break;
11700       case BeginningOfGame:
11701       case MachinePlaysBlack:
11702         if (!WhiteOnMove(forwardMostMove)) {
11703             DisplayError(_("Wait until your turn"), 0);
11704             return;
11705         }
11706         break;
11707       case EditPosition:
11708         EditPositionDone();
11709         break;
11710       case TwoMachinesPlay:
11711         return;
11712       default:
11713         break;
11714     }
11715     SendToProgram("bk\n", &first);
11716     bookOutput[0] = NULLCHAR;
11717     bookRequested = TRUE;
11718 }
11719
11720 void
11721 AboutGameEvent()
11722 {
11723     char *tags = PGNTags(&gameInfo);
11724     TagsPopUp(tags, CmailMsg());
11725     free(tags);
11726 }
11727
11728 /* end button procedures */
11729
11730 void
11731 PrintPosition(fp, move)
11732      FILE *fp;
11733      int move;
11734 {
11735     int i, j;
11736     
11737     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11738         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11739             char c = PieceToChar(boards[move][i][j]);
11740             fputc(c == 'x' ? '.' : c, fp);
11741             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11742         }
11743     }
11744     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11745       fprintf(fp, "white to play\n");
11746     else
11747       fprintf(fp, "black to play\n");
11748 }
11749
11750 void
11751 PrintOpponents(fp)
11752      FILE *fp;
11753 {
11754     if (gameInfo.white != NULL) {
11755         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11756     } else {
11757         fprintf(fp, "\n");
11758     }
11759 }
11760
11761 /* Find last component of program's own name, using some heuristics */
11762 void
11763 TidyProgramName(prog, host, buf)
11764      char *prog, *host, buf[MSG_SIZ];
11765 {
11766     char *p, *q;
11767     int local = (strcmp(host, "localhost") == 0);
11768     while (!local && (p = strchr(prog, ';')) != NULL) {
11769         p++;
11770         while (*p == ' ') p++;
11771         prog = p;
11772     }
11773     if (*prog == '"' || *prog == '\'') {
11774         q = strchr(prog + 1, *prog);
11775     } else {
11776         q = strchr(prog, ' ');
11777     }
11778     if (q == NULL) q = prog + strlen(prog);
11779     p = q;
11780     while (p >= prog && *p != '/' && *p != '\\') p--;
11781     p++;
11782     if(p == prog && *p == '"') p++;
11783     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11784     memcpy(buf, p, q - p);
11785     buf[q - p] = NULLCHAR;
11786     if (!local) {
11787         strcat(buf, "@");
11788         strcat(buf, host);
11789     }
11790 }
11791
11792 char *
11793 TimeControlTagValue()
11794 {
11795     char buf[MSG_SIZ];
11796     if (!appData.clockMode) {
11797         strcpy(buf, "-");
11798     } else if (movesPerSession > 0) {
11799         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11800     } else if (timeIncrement == 0) {
11801         sprintf(buf, "%ld", timeControl/1000);
11802     } else {
11803         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11804     }
11805     return StrSave(buf);
11806 }
11807
11808 void
11809 SetGameInfo()
11810 {
11811     /* This routine is used only for certain modes */
11812     VariantClass v = gameInfo.variant;
11813     ClearGameInfo(&gameInfo);
11814     gameInfo.variant = v;
11815
11816     switch (gameMode) {
11817       case MachinePlaysWhite:
11818         gameInfo.event = StrSave( appData.pgnEventHeader );
11819         gameInfo.site = StrSave(HostName());
11820         gameInfo.date = PGNDate();
11821         gameInfo.round = StrSave("-");
11822         gameInfo.white = StrSave(first.tidy);
11823         gameInfo.black = StrSave(UserName());
11824         gameInfo.timeControl = TimeControlTagValue();
11825         break;
11826
11827       case MachinePlaysBlack:
11828         gameInfo.event = StrSave( appData.pgnEventHeader );
11829         gameInfo.site = StrSave(HostName());
11830         gameInfo.date = PGNDate();
11831         gameInfo.round = StrSave("-");
11832         gameInfo.white = StrSave(UserName());
11833         gameInfo.black = StrSave(first.tidy);
11834         gameInfo.timeControl = TimeControlTagValue();
11835         break;
11836
11837       case TwoMachinesPlay:
11838         gameInfo.event = StrSave( appData.pgnEventHeader );
11839         gameInfo.site = StrSave(HostName());
11840         gameInfo.date = PGNDate();
11841         if (matchGame > 0) {
11842             char buf[MSG_SIZ];
11843             sprintf(buf, "%d", matchGame);
11844             gameInfo.round = StrSave(buf);
11845         } else {
11846             gameInfo.round = StrSave("-");
11847         }
11848         if (first.twoMachinesColor[0] == 'w') {
11849             gameInfo.white = StrSave(first.tidy);
11850             gameInfo.black = StrSave(second.tidy);
11851         } else {
11852             gameInfo.white = StrSave(second.tidy);
11853             gameInfo.black = StrSave(first.tidy);
11854         }
11855         gameInfo.timeControl = TimeControlTagValue();
11856         break;
11857
11858       case EditGame:
11859         gameInfo.event = StrSave("Edited game");
11860         gameInfo.site = StrSave(HostName());
11861         gameInfo.date = PGNDate();
11862         gameInfo.round = StrSave("-");
11863         gameInfo.white = StrSave("-");
11864         gameInfo.black = StrSave("-");
11865         break;
11866
11867       case EditPosition:
11868         gameInfo.event = StrSave("Edited position");
11869         gameInfo.site = StrSave(HostName());
11870         gameInfo.date = PGNDate();
11871         gameInfo.round = StrSave("-");
11872         gameInfo.white = StrSave("-");
11873         gameInfo.black = StrSave("-");
11874         break;
11875
11876       case IcsPlayingWhite:
11877       case IcsPlayingBlack:
11878       case IcsObserving:
11879       case IcsExamining:
11880         break;
11881
11882       case PlayFromGameFile:
11883         gameInfo.event = StrSave("Game from non-PGN file");
11884         gameInfo.site = StrSave(HostName());
11885         gameInfo.date = PGNDate();
11886         gameInfo.round = StrSave("-");
11887         gameInfo.white = StrSave("?");
11888         gameInfo.black = StrSave("?");
11889         break;
11890
11891       default:
11892         break;
11893     }
11894 }
11895
11896 void
11897 ReplaceComment(index, text)
11898      int index;
11899      char *text;
11900 {
11901     int len;
11902
11903     while (*text == '\n') text++;
11904     len = strlen(text);
11905     while (len > 0 && text[len - 1] == '\n') len--;
11906
11907     if (commentList[index] != NULL)
11908       free(commentList[index]);
11909
11910     if (len == 0) {
11911         commentList[index] = NULL;
11912         return;
11913     }
11914     commentList[index] = (char *) malloc(len + 2);
11915     strncpy(commentList[index], text, len);
11916     commentList[index][len] = '\n';
11917     commentList[index][len + 1] = NULLCHAR;
11918 }
11919
11920 void
11921 CrushCRs(text)
11922      char *text;
11923 {
11924   char *p = text;
11925   char *q = text;
11926   char ch;
11927
11928   do {
11929     ch = *p++;
11930     if (ch == '\r') continue;
11931     *q++ = ch;
11932   } while (ch != '\0');
11933 }
11934
11935 void
11936 AppendComment(index, text)
11937      int index;
11938      char *text;
11939 {
11940     int oldlen, len;
11941     char *old;
11942
11943     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11944
11945     CrushCRs(text);
11946     while (*text == '\n') text++;
11947     len = strlen(text);
11948     while (len > 0 && text[len - 1] == '\n') len--;
11949
11950     if (len == 0) return;
11951
11952     if (commentList[index] != NULL) {
11953         old = commentList[index];
11954         oldlen = strlen(old);
11955         commentList[index] = (char *) malloc(oldlen + len + 2);
11956         strcpy(commentList[index], old);
11957         free(old);
11958         strncpy(&commentList[index][oldlen], text, len);
11959         commentList[index][oldlen + len] = '\n';
11960         commentList[index][oldlen + len + 1] = NULLCHAR;
11961     } else {
11962         commentList[index] = (char *) malloc(len + 2);
11963         strncpy(commentList[index], text, len);
11964         commentList[index][len] = '\n';
11965         commentList[index][len + 1] = NULLCHAR;
11966     }
11967 }
11968
11969 static char * FindStr( char * text, char * sub_text )
11970 {
11971     char * result = strstr( text, sub_text );
11972
11973     if( result != NULL ) {
11974         result += strlen( sub_text );
11975     }
11976
11977     return result;
11978 }
11979
11980 /* [AS] Try to extract PV info from PGN comment */
11981 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11982 char *GetInfoFromComment( int index, char * text )
11983 {
11984     char * sep = text;
11985
11986     if( text != NULL && index > 0 ) {
11987         int score = 0;
11988         int depth = 0;
11989         int time = -1, sec = 0, deci;
11990         char * s_eval = FindStr( text, "[%eval " );
11991         char * s_emt = FindStr( text, "[%emt " );
11992
11993         if( s_eval != NULL || s_emt != NULL ) {
11994             /* New style */
11995             char delim;
11996
11997             if( s_eval != NULL ) {
11998                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11999                     return text;
12000                 }
12001
12002                 if( delim != ']' ) {
12003                     return text;
12004                 }
12005             }
12006
12007             if( s_emt != NULL ) {
12008             }
12009         }
12010         else {
12011             /* We expect something like: [+|-]nnn.nn/dd */
12012             int score_lo = 0;
12013
12014             sep = strchr( text, '/' );
12015             if( sep == NULL || sep < (text+4) ) {
12016                 return text;
12017             }
12018
12019             time = -1; sec = -1; deci = -1;
12020             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12021                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12022                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12023                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12024                 return text;
12025             }
12026
12027             if( score_lo < 0 || score_lo >= 100 ) {
12028                 return text;
12029             }
12030
12031             if(sec >= 0) time = 600*time + 10*sec; else
12032             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12033
12034             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12035
12036             /* [HGM] PV time: now locate end of PV info */
12037             while( *++sep >= '0' && *sep <= '9'); // strip depth
12038             if(time >= 0)
12039             while( *++sep >= '0' && *sep <= '9'); // strip time
12040             if(sec >= 0)
12041             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12042             if(deci >= 0)
12043             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12044             while(*sep == ' ') sep++;
12045         }
12046
12047         if( depth <= 0 ) {
12048             return text;
12049         }
12050
12051         if( time < 0 ) {
12052             time = -1;
12053         }
12054
12055         pvInfoList[index-1].depth = depth;
12056         pvInfoList[index-1].score = score;
12057         pvInfoList[index-1].time  = 10*time; // centi-sec
12058     }
12059     return sep;
12060 }
12061
12062 void
12063 SendToProgram(message, cps)
12064      char *message;
12065      ChessProgramState *cps;
12066 {
12067     int count, outCount, error;
12068     char buf[MSG_SIZ];
12069
12070     if (cps->pr == NULL) return;
12071     Attention(cps);
12072     
12073     if (appData.debugMode) {
12074         TimeMark now;
12075         GetTimeMark(&now);
12076         fprintf(debugFP, "%ld >%-6s: %s", 
12077                 SubtractTimeMarks(&now, &programStartTime),
12078                 cps->which, message);
12079     }
12080     
12081     count = strlen(message);
12082     outCount = OutputToProcess(cps->pr, message, count, &error);
12083     if (outCount < count && !exiting 
12084                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12085         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12086         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12087             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12088                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12089                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12090             } else {
12091                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12092             }
12093             gameInfo.resultDetails = buf;
12094         }
12095         DisplayFatalError(buf, error, 1);
12096     }
12097 }
12098
12099 void
12100 ReceiveFromProgram(isr, closure, message, count, error)
12101      InputSourceRef isr;
12102      VOIDSTAR closure;
12103      char *message;
12104      int count;
12105      int error;
12106 {
12107     char *end_str;
12108     char buf[MSG_SIZ];
12109     ChessProgramState *cps = (ChessProgramState *)closure;
12110
12111     if (isr != cps->isr) return; /* Killed intentionally */
12112     if (count <= 0) {
12113         if (count == 0) {
12114             sprintf(buf,
12115                     _("Error: %s chess program (%s) exited unexpectedly"),
12116                     cps->which, cps->program);
12117         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12118                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12119                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12120                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12121                 } else {
12122                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12123                 }
12124                 gameInfo.resultDetails = buf;
12125             }
12126             RemoveInputSource(cps->isr);
12127             DisplayFatalError(buf, 0, 1);
12128         } else {
12129             sprintf(buf,
12130                     _("Error reading from %s chess program (%s)"),
12131                     cps->which, cps->program);
12132             RemoveInputSource(cps->isr);
12133
12134             /* [AS] Program is misbehaving badly... kill it */
12135             if( count == -2 ) {
12136                 DestroyChildProcess( cps->pr, 9 );
12137                 cps->pr = NoProc;
12138             }
12139
12140             DisplayFatalError(buf, error, 1);
12141         }
12142         return;
12143     }
12144     
12145     if ((end_str = strchr(message, '\r')) != NULL)
12146       *end_str = NULLCHAR;
12147     if ((end_str = strchr(message, '\n')) != NULL)
12148       *end_str = NULLCHAR;
12149     
12150     if (appData.debugMode) {
12151         TimeMark now; int print = 1;
12152         char *quote = ""; char c; int i;
12153
12154         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12155                 char start = message[0];
12156                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12157                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12158                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12159                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12160                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12161                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12162                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12163                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12164                         { quote = "# "; print = (appData.engineComments == 2); }
12165                 message[0] = start; // restore original message
12166         }
12167         if(print) {
12168                 GetTimeMark(&now);
12169                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12170                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12171                         quote,
12172                         message);
12173         }
12174     }
12175
12176     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12177     if (appData.icsEngineAnalyze) {
12178         if (strstr(message, "whisper") != NULL ||
12179              strstr(message, "kibitz") != NULL || 
12180             strstr(message, "tellics") != NULL) return;
12181     }
12182
12183     HandleMachineMove(message, cps);
12184 }
12185
12186
12187 void
12188 SendTimeControl(cps, mps, tc, inc, sd, st)
12189      ChessProgramState *cps;
12190      int mps, inc, sd, st;
12191      long tc;
12192 {
12193     char buf[MSG_SIZ];
12194     int seconds;
12195
12196     if( timeControl_2 > 0 ) {
12197         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12198             tc = timeControl_2;
12199         }
12200     }
12201     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12202     inc /= cps->timeOdds;
12203     st  /= cps->timeOdds;
12204
12205     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12206
12207     if (st > 0) {
12208       /* Set exact time per move, normally using st command */
12209       if (cps->stKludge) {
12210         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12211         seconds = st % 60;
12212         if (seconds == 0) {
12213           sprintf(buf, "level 1 %d\n", st/60);
12214         } else {
12215           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12216         }
12217       } else {
12218         sprintf(buf, "st %d\n", st);
12219       }
12220     } else {
12221       /* Set conventional or incremental time control, using level command */
12222       if (seconds == 0) {
12223         /* Note old gnuchess bug -- minutes:seconds used to not work.
12224            Fixed in later versions, but still avoid :seconds
12225            when seconds is 0. */
12226         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12227       } else {
12228         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12229                 seconds, inc/1000);
12230       }
12231     }
12232     SendToProgram(buf, cps);
12233
12234     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12235     /* Orthogonally, limit search to given depth */
12236     if (sd > 0) {
12237       if (cps->sdKludge) {
12238         sprintf(buf, "depth\n%d\n", sd);
12239       } else {
12240         sprintf(buf, "sd %d\n", sd);
12241       }
12242       SendToProgram(buf, cps);
12243     }
12244
12245     if(cps->nps > 0) { /* [HGM] nps */
12246         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12247         else {
12248                 sprintf(buf, "nps %d\n", cps->nps);
12249               SendToProgram(buf, cps);
12250         }
12251     }
12252 }
12253
12254 ChessProgramState *WhitePlayer()
12255 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12256 {
12257     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12258        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12259         return &second;
12260     return &first;
12261 }
12262
12263 void
12264 SendTimeRemaining(cps, machineWhite)
12265      ChessProgramState *cps;
12266      int /*boolean*/ machineWhite;
12267 {
12268     char message[MSG_SIZ];
12269     long time, otime;
12270
12271     /* Note: this routine must be called when the clocks are stopped
12272        or when they have *just* been set or switched; otherwise
12273        it will be off by the time since the current tick started.
12274     */
12275     if (machineWhite) {
12276         time = whiteTimeRemaining / 10;
12277         otime = blackTimeRemaining / 10;
12278     } else {
12279         time = blackTimeRemaining / 10;
12280         otime = whiteTimeRemaining / 10;
12281     }
12282     /* [HGM] translate opponent's time by time-odds factor */
12283     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12284     if (appData.debugMode) {
12285         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12286     }
12287
12288     if (time <= 0) time = 1;
12289     if (otime <= 0) otime = 1;
12290     
12291     sprintf(message, "time %ld\n", time);
12292     SendToProgram(message, cps);
12293
12294     sprintf(message, "otim %ld\n", otime);
12295     SendToProgram(message, cps);
12296 }
12297
12298 int
12299 BoolFeature(p, name, loc, cps)
12300      char **p;
12301      char *name;
12302      int *loc;
12303      ChessProgramState *cps;
12304 {
12305   char buf[MSG_SIZ];
12306   int len = strlen(name);
12307   int val;
12308   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12309     (*p) += len + 1;
12310     sscanf(*p, "%d", &val);
12311     *loc = (val != 0);
12312     while (**p && **p != ' ') (*p)++;
12313     sprintf(buf, "accepted %s\n", name);
12314     SendToProgram(buf, cps);
12315     return TRUE;
12316   }
12317   return FALSE;
12318 }
12319
12320 int
12321 IntFeature(p, name, loc, cps)
12322      char **p;
12323      char *name;
12324      int *loc;
12325      ChessProgramState *cps;
12326 {
12327   char buf[MSG_SIZ];
12328   int len = strlen(name);
12329   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12330     (*p) += len + 1;
12331     sscanf(*p, "%d", loc);
12332     while (**p && **p != ' ') (*p)++;
12333     sprintf(buf, "accepted %s\n", name);
12334     SendToProgram(buf, cps);
12335     return TRUE;
12336   }
12337   return FALSE;
12338 }
12339
12340 int
12341 StringFeature(p, name, loc, cps)
12342      char **p;
12343      char *name;
12344      char loc[];
12345      ChessProgramState *cps;
12346 {
12347   char buf[MSG_SIZ];
12348   int len = strlen(name);
12349   if (strncmp((*p), name, len) == 0
12350       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12351     (*p) += len + 2;
12352     sscanf(*p, "%[^\"]", loc);
12353     while (**p && **p != '\"') (*p)++;
12354     if (**p == '\"') (*p)++;
12355     sprintf(buf, "accepted %s\n", name);
12356     SendToProgram(buf, cps);
12357     return TRUE;
12358   }
12359   return FALSE;
12360 }
12361
12362 int 
12363 ParseOption(Option *opt, ChessProgramState *cps)
12364 // [HGM] options: process the string that defines an engine option, and determine
12365 // name, type, default value, and allowed value range
12366 {
12367         char *p, *q, buf[MSG_SIZ];
12368         int n, min = (-1)<<31, max = 1<<31, def;
12369
12370         if(p = strstr(opt->name, " -spin ")) {
12371             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12372             if(max < min) max = min; // enforce consistency
12373             if(def < min) def = min;
12374             if(def > max) def = max;
12375             opt->value = def;
12376             opt->min = min;
12377             opt->max = max;
12378             opt->type = Spin;
12379         } else if((p = strstr(opt->name, " -slider "))) {
12380             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12381             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12382             if(max < min) max = min; // enforce consistency
12383             if(def < min) def = min;
12384             if(def > max) def = max;
12385             opt->value = def;
12386             opt->min = min;
12387             opt->max = max;
12388             opt->type = Spin; // Slider;
12389         } else if((p = strstr(opt->name, " -string "))) {
12390             opt->textValue = p+9;
12391             opt->type = TextBox;
12392         } else if((p = strstr(opt->name, " -file "))) {
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; // FileName;
12396         } else if((p = strstr(opt->name, " -path "))) {
12397             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12398             opt->textValue = p+7;
12399             opt->type = TextBox; // PathName;
12400         } else if(p = strstr(opt->name, " -check ")) {
12401             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12402             opt->value = (def != 0);
12403             opt->type = CheckBox;
12404         } else if(p = strstr(opt->name, " -combo ")) {
12405             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12406             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12407             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12408             opt->value = n = 0;
12409             while(q = StrStr(q, " /// ")) {
12410                 n++; *q = 0;    // count choices, and null-terminate each of them
12411                 q += 5;
12412                 if(*q == '*') { // remember default, which is marked with * prefix
12413                     q++;
12414                     opt->value = n;
12415                 }
12416                 cps->comboList[cps->comboCnt++] = q;
12417             }
12418             cps->comboList[cps->comboCnt++] = NULL;
12419             opt->max = n + 1;
12420             opt->type = ComboBox;
12421         } else if(p = strstr(opt->name, " -button")) {
12422             opt->type = Button;
12423         } else if(p = strstr(opt->name, " -save")) {
12424             opt->type = SaveButton;
12425         } else return FALSE;
12426         *p = 0; // terminate option name
12427         // now look if the command-line options define a setting for this engine option.
12428         if(cps->optionSettings && cps->optionSettings[0])
12429             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12430         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12431                 sprintf(buf, "option %s", p);
12432                 if(p = strstr(buf, ",")) *p = 0;
12433                 strcat(buf, "\n");
12434                 SendToProgram(buf, cps);
12435         }
12436         return TRUE;
12437 }
12438
12439 void
12440 FeatureDone(cps, val)
12441      ChessProgramState* cps;
12442      int val;
12443 {
12444   DelayedEventCallback cb = GetDelayedEvent();
12445   if ((cb == InitBackEnd3 && cps == &first) ||
12446       (cb == TwoMachinesEventIfReady && cps == &second)) {
12447     CancelDelayedEvent();
12448     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12449   }
12450   cps->initDone = val;
12451 }
12452
12453 /* Parse feature command from engine */
12454 void
12455 ParseFeatures(args, cps)
12456      char* args;
12457      ChessProgramState *cps;  
12458 {
12459   char *p = args;
12460   char *q;
12461   int val;
12462   char buf[MSG_SIZ];
12463
12464   for (;;) {
12465     while (*p == ' ') p++;
12466     if (*p == NULLCHAR) return;
12467
12468     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12469     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12470     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12471     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12472     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12473     if (BoolFeature(&p, "reuse", &val, cps)) {
12474       /* Engine can disable reuse, but can't enable it if user said no */
12475       if (!val) cps->reuse = FALSE;
12476       continue;
12477     }
12478     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12479     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12480       if (gameMode == TwoMachinesPlay) {
12481         DisplayTwoMachinesTitle();
12482       } else {
12483         DisplayTitle("");
12484       }
12485       continue;
12486     }
12487     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12488     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12489     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12490     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12491     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12492     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12493     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12494     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12495     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12496     if (IntFeature(&p, "done", &val, cps)) {
12497       FeatureDone(cps, val);
12498       continue;
12499     }
12500     /* Added by Tord: */
12501     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12502     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12503     /* End of additions by Tord */
12504
12505     /* [HGM] added features: */
12506     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12507     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12508     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12509     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12510     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12511     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12512     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12513         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12514             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12515             SendToProgram(buf, cps);
12516             continue;
12517         }
12518         if(cps->nrOptions >= MAX_OPTIONS) {
12519             cps->nrOptions--;
12520             sprintf(buf, "%s engine has too many options\n", cps->which);
12521             DisplayError(buf, 0);
12522         }
12523         continue;
12524     }
12525     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12526     /* End of additions by HGM */
12527
12528     /* unknown feature: complain and skip */
12529     q = p;
12530     while (*q && *q != '=') q++;
12531     sprintf(buf, "rejected %.*s\n", q-p, p);
12532     SendToProgram(buf, cps);
12533     p = q;
12534     if (*p == '=') {
12535       p++;
12536       if (*p == '\"') {
12537         p++;
12538         while (*p && *p != '\"') p++;
12539         if (*p == '\"') p++;
12540       } else {
12541         while (*p && *p != ' ') p++;
12542       }
12543     }
12544   }
12545
12546 }
12547
12548 void
12549 PeriodicUpdatesEvent(newState)
12550      int newState;
12551 {
12552     if (newState == appData.periodicUpdates)
12553       return;
12554
12555     appData.periodicUpdates=newState;
12556
12557     /* Display type changes, so update it now */
12558     DisplayAnalysis();
12559
12560     /* Get the ball rolling again... */
12561     if (newState) {
12562         AnalysisPeriodicEvent(1);
12563         StartAnalysisClock();
12564     }
12565 }
12566
12567 void
12568 PonderNextMoveEvent(newState)
12569      int newState;
12570 {
12571     if (newState == appData.ponderNextMove) return;
12572     if (gameMode == EditPosition) EditPositionDone();
12573     if (newState) {
12574         SendToProgram("hard\n", &first);
12575         if (gameMode == TwoMachinesPlay) {
12576             SendToProgram("hard\n", &second);
12577         }
12578     } else {
12579         SendToProgram("easy\n", &first);
12580         thinkOutput[0] = NULLCHAR;
12581         if (gameMode == TwoMachinesPlay) {
12582             SendToProgram("easy\n", &second);
12583         }
12584     }
12585     appData.ponderNextMove = newState;
12586 }
12587
12588 void
12589 NewSettingEvent(option, command, value)
12590      char *command;
12591      int option, value;
12592 {
12593     char buf[MSG_SIZ];
12594
12595     if (gameMode == EditPosition) EditPositionDone();
12596     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12597     SendToProgram(buf, &first);
12598     if (gameMode == TwoMachinesPlay) {
12599         SendToProgram(buf, &second);
12600     }
12601 }
12602
12603 void
12604 ShowThinkingEvent()
12605 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12606 {
12607     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12608     int newState = appData.showThinking
12609         // [HGM] thinking: other features now need thinking output as well
12610         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12611     
12612     if (oldState == newState) return;
12613     oldState = newState;
12614     if (gameMode == EditPosition) EditPositionDone();
12615     if (oldState) {
12616         SendToProgram("post\n", &first);
12617         if (gameMode == TwoMachinesPlay) {
12618             SendToProgram("post\n", &second);
12619         }
12620     } else {
12621         SendToProgram("nopost\n", &first);
12622         thinkOutput[0] = NULLCHAR;
12623         if (gameMode == TwoMachinesPlay) {
12624             SendToProgram("nopost\n", &second);
12625         }
12626     }
12627 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12628 }
12629
12630 void
12631 AskQuestionEvent(title, question, replyPrefix, which)
12632      char *title; char *question; char *replyPrefix; char *which;
12633 {
12634   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12635   if (pr == NoProc) return;
12636   AskQuestion(title, question, replyPrefix, pr);
12637 }
12638
12639 void
12640 DisplayMove(moveNumber)
12641      int moveNumber;
12642 {
12643     char message[MSG_SIZ];
12644     char res[MSG_SIZ];
12645     char cpThinkOutput[MSG_SIZ];
12646
12647     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12648     
12649     if (moveNumber == forwardMostMove - 1 || 
12650         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12651
12652         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12653
12654         if (strchr(cpThinkOutput, '\n')) {
12655             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12656         }
12657     } else {
12658         *cpThinkOutput = NULLCHAR;
12659     }
12660
12661     /* [AS] Hide thinking from human user */
12662     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12663         *cpThinkOutput = NULLCHAR;
12664         if( thinkOutput[0] != NULLCHAR ) {
12665             int i;
12666
12667             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12668                 cpThinkOutput[i] = '.';
12669             }
12670             cpThinkOutput[i] = NULLCHAR;
12671             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12672         }
12673     }
12674
12675     if (moveNumber == forwardMostMove - 1 &&
12676         gameInfo.resultDetails != NULL) {
12677         if (gameInfo.resultDetails[0] == NULLCHAR) {
12678             sprintf(res, " %s", PGNResult(gameInfo.result));
12679         } else {
12680             sprintf(res, " {%s} %s",
12681                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12682         }
12683     } else {
12684         res[0] = NULLCHAR;
12685     }
12686
12687     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12688         DisplayMessage(res, cpThinkOutput);
12689     } else {
12690         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12691                 WhiteOnMove(moveNumber) ? " " : ".. ",
12692                 parseList[moveNumber], res);
12693         DisplayMessage(message, cpThinkOutput);
12694     }
12695 }
12696
12697 void
12698 DisplayAnalysisText(text)
12699      char *text;
12700 {
12701     char buf[MSG_SIZ];
12702
12703     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12704                || appData.icsEngineAnalyze) {
12705         sprintf(buf, "Analysis (%s)", first.tidy);
12706         AnalysisPopUp(buf, text);
12707     }
12708 }
12709
12710 static int
12711 only_one_move(str)
12712      char *str;
12713 {
12714     while (*str && isspace(*str)) ++str;
12715     while (*str && !isspace(*str)) ++str;
12716     if (!*str) return 1;
12717     while (*str && isspace(*str)) ++str;
12718     if (!*str) return 1;
12719     return 0;
12720 }
12721
12722 void
12723 DisplayAnalysis()
12724 {
12725     char buf[MSG_SIZ];
12726     char lst[MSG_SIZ / 2];
12727     double nps;
12728     static char *xtra[] = { "", " (--)", " (++)" };
12729     int h, m, s, cs;
12730   
12731     if (programStats.time == 0) {
12732         programStats.time = 1;
12733     }
12734   
12735     if (programStats.got_only_move) {
12736         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12737     } else {
12738         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12739
12740         nps = (u64ToDouble(programStats.nodes) /
12741              ((double)programStats.time /100.0));
12742
12743         cs = programStats.time % 100;
12744         s = programStats.time / 100;
12745         h = (s / (60*60));
12746         s = s - h*60*60;
12747         m = (s/60);
12748         s = s - m*60;
12749
12750         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12751           if (programStats.move_name[0] != NULLCHAR) {
12752             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12753                     programStats.depth,
12754                     programStats.nr_moves-programStats.moves_left,
12755                     programStats.nr_moves, programStats.move_name,
12756                     ((float)programStats.score)/100.0, lst,
12757                     only_one_move(lst)?
12758                     xtra[programStats.got_fail] : "",
12759                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12760           } else {
12761             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12762                     programStats.depth,
12763                     programStats.nr_moves-programStats.moves_left,
12764                     programStats.nr_moves, ((float)programStats.score)/100.0,
12765                     lst,
12766                     only_one_move(lst)?
12767                     xtra[programStats.got_fail] : "",
12768                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12769           }
12770         } else {
12771             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12772                     programStats.depth,
12773                     ((float)programStats.score)/100.0,
12774                     lst,
12775                     only_one_move(lst)?
12776                     xtra[programStats.got_fail] : "",
12777                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12778         }
12779     }
12780     DisplayAnalysisText(buf);
12781 }
12782
12783 void
12784 DisplayComment(moveNumber, text)
12785      int moveNumber;
12786      char *text;
12787 {
12788     char title[MSG_SIZ];
12789     char buf[8000]; // comment can be long!
12790     int score, depth;
12791
12792     if( appData.autoDisplayComment ) {
12793         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12794             strcpy(title, "Comment");
12795         } else {
12796             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12797                     WhiteOnMove(moveNumber) ? " " : ".. ",
12798                     parseList[moveNumber]);
12799         }
12800         // [HGM] PV info: display PV info together with (or as) comment
12801         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12802             if(text == NULL) text = "";                                           
12803             score = pvInfoList[moveNumber].score;
12804             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12805                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12806             text = buf;
12807         }
12808     } else title[0] = 0;
12809
12810     if (text != NULL)
12811         CommentPopUp(title, text);
12812 }
12813
12814 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12815  * might be busy thinking or pondering.  It can be omitted if your
12816  * gnuchess is configured to stop thinking immediately on any user
12817  * input.  However, that gnuchess feature depends on the FIONREAD
12818  * ioctl, which does not work properly on some flavors of Unix.
12819  */
12820 void
12821 Attention(cps)
12822      ChessProgramState *cps;
12823 {
12824 #if ATTENTION
12825     if (!cps->useSigint) return;
12826     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12827     switch (gameMode) {
12828       case MachinePlaysWhite:
12829       case MachinePlaysBlack:
12830       case TwoMachinesPlay:
12831       case IcsPlayingWhite:
12832       case IcsPlayingBlack:
12833       case AnalyzeMode:
12834       case AnalyzeFile:
12835         /* Skip if we know it isn't thinking */
12836         if (!cps->maybeThinking) return;
12837         if (appData.debugMode)
12838           fprintf(debugFP, "Interrupting %s\n", cps->which);
12839         InterruptChildProcess(cps->pr);
12840         cps->maybeThinking = FALSE;
12841         break;
12842       default:
12843         break;
12844     }
12845 #endif /*ATTENTION*/
12846 }
12847
12848 int
12849 CheckFlags()
12850 {
12851     if (whiteTimeRemaining <= 0) {
12852         if (!whiteFlag) {
12853             whiteFlag = TRUE;
12854             if (appData.icsActive) {
12855                 if (appData.autoCallFlag &&
12856                     gameMode == IcsPlayingBlack && !blackFlag) {
12857                   SendToICS(ics_prefix);
12858                   SendToICS("flag\n");
12859                 }
12860             } else {
12861                 if (blackFlag) {
12862                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12863                 } else {
12864                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12865                     if (appData.autoCallFlag) {
12866                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12867                         return TRUE;
12868                     }
12869                 }
12870             }
12871         }
12872     }
12873     if (blackTimeRemaining <= 0) {
12874         if (!blackFlag) {
12875             blackFlag = TRUE;
12876             if (appData.icsActive) {
12877                 if (appData.autoCallFlag &&
12878                     gameMode == IcsPlayingWhite && !whiteFlag) {
12879                   SendToICS(ics_prefix);
12880                   SendToICS("flag\n");
12881                 }
12882             } else {
12883                 if (whiteFlag) {
12884                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12885                 } else {
12886                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12887                     if (appData.autoCallFlag) {
12888                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12889                         return TRUE;
12890                     }
12891                 }
12892             }
12893         }
12894     }
12895     return FALSE;
12896 }
12897
12898 void
12899 CheckTimeControl()
12900 {
12901     if (!appData.clockMode || appData.icsActive ||
12902         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12903
12904     /*
12905      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12906      */
12907     if ( !WhiteOnMove(forwardMostMove) )
12908         /* White made time control */
12909         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12910         /* [HGM] time odds: correct new time quota for time odds! */
12911                                             / WhitePlayer()->timeOdds;
12912       else
12913         /* Black made time control */
12914         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12915                                             / WhitePlayer()->other->timeOdds;
12916 }
12917
12918 void
12919 DisplayBothClocks()
12920 {
12921     int wom = gameMode == EditPosition ?
12922       !blackPlaysFirst : WhiteOnMove(currentMove);
12923     DisplayWhiteClock(whiteTimeRemaining, wom);
12924     DisplayBlackClock(blackTimeRemaining, !wom);
12925 }
12926
12927
12928 /* Timekeeping seems to be a portability nightmare.  I think everyone
12929    has ftime(), but I'm really not sure, so I'm including some ifdefs
12930    to use other calls if you don't.  Clocks will be less accurate if
12931    you have neither ftime nor gettimeofday.
12932 */
12933
12934 /* VS 2008 requires the #include outside of the function */
12935 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12936 #include <sys/timeb.h>
12937 #endif
12938
12939 /* Get the current time as a TimeMark */
12940 void
12941 GetTimeMark(tm)
12942      TimeMark *tm;
12943 {
12944 #if HAVE_GETTIMEOFDAY
12945
12946     struct timeval timeVal;
12947     struct timezone timeZone;
12948
12949     gettimeofday(&timeVal, &timeZone);
12950     tm->sec = (long) timeVal.tv_sec; 
12951     tm->ms = (int) (timeVal.tv_usec / 1000L);
12952
12953 #else /*!HAVE_GETTIMEOFDAY*/
12954 #if HAVE_FTIME
12955
12956 // include <sys/timeb.h> / moved to just above start of function
12957     struct timeb timeB;
12958
12959     ftime(&timeB);
12960     tm->sec = (long) timeB.time;
12961     tm->ms = (int) timeB.millitm;
12962
12963 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12964     tm->sec = (long) time(NULL);
12965     tm->ms = 0;
12966 #endif
12967 #endif
12968 }
12969
12970 /* Return the difference in milliseconds between two
12971    time marks.  We assume the difference will fit in a long!
12972 */
12973 long
12974 SubtractTimeMarks(tm2, tm1)
12975      TimeMark *tm2, *tm1;
12976 {
12977     return 1000L*(tm2->sec - tm1->sec) +
12978            (long) (tm2->ms - tm1->ms);
12979 }
12980
12981
12982 /*
12983  * Code to manage the game clocks.
12984  *
12985  * In tournament play, black starts the clock and then white makes a move.
12986  * We give the human user a slight advantage if he is playing white---the
12987  * clocks don't run until he makes his first move, so it takes zero time.
12988  * Also, we don't account for network lag, so we could get out of sync
12989  * with GNU Chess's clock -- but then, referees are always right.  
12990  */
12991
12992 static TimeMark tickStartTM;
12993 static long intendedTickLength;
12994
12995 long
12996 NextTickLength(timeRemaining)
12997      long timeRemaining;
12998 {
12999     long nominalTickLength, nextTickLength;
13000
13001     if (timeRemaining > 0L && timeRemaining <= 10000L)
13002       nominalTickLength = 100L;
13003     else
13004       nominalTickLength = 1000L;
13005     nextTickLength = timeRemaining % nominalTickLength;
13006     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13007
13008     return nextTickLength;
13009 }
13010
13011 /* Adjust clock one minute up or down */
13012 void
13013 AdjustClock(Boolean which, int dir)
13014 {
13015     if(which) blackTimeRemaining += 60000*dir;
13016     else      whiteTimeRemaining += 60000*dir;
13017     DisplayBothClocks();
13018 }
13019
13020 /* Stop clocks and reset to a fresh time control */
13021 void
13022 ResetClocks() 
13023 {
13024     (void) StopClockTimer();
13025     if (appData.icsActive) {
13026         whiteTimeRemaining = blackTimeRemaining = 0;
13027     } else { /* [HGM] correct new time quote for time odds */
13028         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13029         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13030     }
13031     if (whiteFlag || blackFlag) {
13032         DisplayTitle("");
13033         whiteFlag = blackFlag = FALSE;
13034     }
13035     DisplayBothClocks();
13036 }
13037
13038 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13039
13040 /* Decrement running clock by amount of time that has passed */
13041 void
13042 DecrementClocks()
13043 {
13044     long timeRemaining;
13045     long lastTickLength, fudge;
13046     TimeMark now;
13047
13048     if (!appData.clockMode) return;
13049     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13050         
13051     GetTimeMark(&now);
13052
13053     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13054
13055     /* Fudge if we woke up a little too soon */
13056     fudge = intendedTickLength - lastTickLength;
13057     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13058
13059     if (WhiteOnMove(forwardMostMove)) {
13060         if(whiteNPS >= 0) lastTickLength = 0;
13061         timeRemaining = whiteTimeRemaining -= lastTickLength;
13062         DisplayWhiteClock(whiteTimeRemaining - fudge,
13063                           WhiteOnMove(currentMove));
13064     } else {
13065         if(blackNPS >= 0) lastTickLength = 0;
13066         timeRemaining = blackTimeRemaining -= lastTickLength;
13067         DisplayBlackClock(blackTimeRemaining - fudge,
13068                           !WhiteOnMove(currentMove));
13069     }
13070
13071     if (CheckFlags()) return;
13072         
13073     tickStartTM = now;
13074     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13075     StartClockTimer(intendedTickLength);
13076
13077     /* if the time remaining has fallen below the alarm threshold, sound the
13078      * alarm. if the alarm has sounded and (due to a takeback or time control
13079      * with increment) the time remaining has increased to a level above the
13080      * threshold, reset the alarm so it can sound again. 
13081      */
13082     
13083     if (appData.icsActive && appData.icsAlarm) {
13084
13085         /* make sure we are dealing with the user's clock */
13086         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13087                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13088            )) return;
13089
13090         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13091             alarmSounded = FALSE;
13092         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13093             PlayAlarmSound();
13094             alarmSounded = TRUE;
13095         }
13096     }
13097 }
13098
13099
13100 /* A player has just moved, so stop the previously running
13101    clock and (if in clock mode) start the other one.
13102    We redisplay both clocks in case we're in ICS mode, because
13103    ICS gives us an update to both clocks after every move.
13104    Note that this routine is called *after* forwardMostMove
13105    is updated, so the last fractional tick must be subtracted
13106    from the color that is *not* on move now.
13107 */
13108 void
13109 SwitchClocks()
13110 {
13111     long lastTickLength;
13112     TimeMark now;
13113     int flagged = FALSE;
13114
13115     GetTimeMark(&now);
13116
13117     if (StopClockTimer() && appData.clockMode) {
13118         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13119         if (WhiteOnMove(forwardMostMove)) {
13120             if(blackNPS >= 0) lastTickLength = 0;
13121             blackTimeRemaining -= lastTickLength;
13122            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13123 //         if(pvInfoList[forwardMostMove-1].time == -1)
13124                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13125                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13126         } else {
13127            if(whiteNPS >= 0) lastTickLength = 0;
13128            whiteTimeRemaining -= lastTickLength;
13129            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13130 //         if(pvInfoList[forwardMostMove-1].time == -1)
13131                  pvInfoList[forwardMostMove-1].time = 
13132                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13133         }
13134         flagged = CheckFlags();
13135     }
13136     CheckTimeControl();
13137
13138     if (flagged || !appData.clockMode) return;
13139
13140     switch (gameMode) {
13141       case MachinePlaysBlack:
13142       case MachinePlaysWhite:
13143       case BeginningOfGame:
13144         if (pausing) return;
13145         break;
13146
13147       case EditGame:
13148       case PlayFromGameFile:
13149       case IcsExamining:
13150         return;
13151
13152       default:
13153         break;
13154     }
13155
13156     tickStartTM = now;
13157     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13158       whiteTimeRemaining : blackTimeRemaining);
13159     StartClockTimer(intendedTickLength);
13160 }
13161         
13162
13163 /* Stop both clocks */
13164 void
13165 StopClocks()
13166 {       
13167     long lastTickLength;
13168     TimeMark now;
13169
13170     if (!StopClockTimer()) return;
13171     if (!appData.clockMode) return;
13172
13173     GetTimeMark(&now);
13174
13175     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13176     if (WhiteOnMove(forwardMostMove)) {
13177         if(whiteNPS >= 0) lastTickLength = 0;
13178         whiteTimeRemaining -= lastTickLength;
13179         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13180     } else {
13181         if(blackNPS >= 0) lastTickLength = 0;
13182         blackTimeRemaining -= lastTickLength;
13183         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13184     }
13185     CheckFlags();
13186 }
13187         
13188 /* Start clock of player on move.  Time may have been reset, so
13189    if clock is already running, stop and restart it. */
13190 void
13191 StartClocks()
13192 {
13193     (void) StopClockTimer(); /* in case it was running already */
13194     DisplayBothClocks();
13195     if (CheckFlags()) return;
13196
13197     if (!appData.clockMode) return;
13198     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13199
13200     GetTimeMark(&tickStartTM);
13201     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13202       whiteTimeRemaining : blackTimeRemaining);
13203
13204    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13205     whiteNPS = blackNPS = -1; 
13206     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13207        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13208         whiteNPS = first.nps;
13209     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13210        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13211         blackNPS = first.nps;
13212     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13213         whiteNPS = second.nps;
13214     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13215         blackNPS = second.nps;
13216     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13217
13218     StartClockTimer(intendedTickLength);
13219 }
13220
13221 char *
13222 TimeString(ms)
13223      long ms;
13224 {
13225     long second, minute, hour, day;
13226     char *sign = "";
13227     static char buf[32];
13228     
13229     if (ms > 0 && ms <= 9900) {
13230       /* convert milliseconds to tenths, rounding up */
13231       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13232
13233       sprintf(buf, " %03.1f ", tenths/10.0);
13234       return buf;
13235     }
13236
13237     /* convert milliseconds to seconds, rounding up */
13238     /* use floating point to avoid strangeness of integer division
13239        with negative dividends on many machines */
13240     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13241
13242     if (second < 0) {
13243         sign = "-";
13244         second = -second;
13245     }
13246     
13247     day = second / (60 * 60 * 24);
13248     second = second % (60 * 60 * 24);
13249     hour = second / (60 * 60);
13250     second = second % (60 * 60);
13251     minute = second / 60;
13252     second = second % 60;
13253     
13254     if (day > 0)
13255       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13256               sign, day, hour, minute, second);
13257     else if (hour > 0)
13258       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13259     else
13260       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13261     
13262     return buf;
13263 }
13264
13265
13266 /*
13267  * This is necessary because some C libraries aren't ANSI C compliant yet.
13268  */
13269 char *
13270 StrStr(string, match)
13271      char *string, *match;
13272 {
13273     int i, length;
13274     
13275     length = strlen(match);
13276     
13277     for (i = strlen(string) - length; i >= 0; i--, string++)
13278       if (!strncmp(match, string, length))
13279         return string;
13280     
13281     return NULL;
13282 }
13283
13284 char *
13285 StrCaseStr(string, match)
13286      char *string, *match;
13287 {
13288     int i, j, length;
13289     
13290     length = strlen(match);
13291     
13292     for (i = strlen(string) - length; i >= 0; i--, string++) {
13293         for (j = 0; j < length; j++) {
13294             if (ToLower(match[j]) != ToLower(string[j]))
13295               break;
13296         }
13297         if (j == length) return string;
13298     }
13299
13300     return NULL;
13301 }
13302
13303 #ifndef _amigados
13304 int
13305 StrCaseCmp(s1, s2)
13306      char *s1, *s2;
13307 {
13308     char c1, c2;
13309     
13310     for (;;) {
13311         c1 = ToLower(*s1++);
13312         c2 = ToLower(*s2++);
13313         if (c1 > c2) return 1;
13314         if (c1 < c2) return -1;
13315         if (c1 == NULLCHAR) return 0;
13316     }
13317 }
13318
13319
13320 int
13321 ToLower(c)
13322      int c;
13323 {
13324     return isupper(c) ? tolower(c) : c;
13325 }
13326
13327
13328 int
13329 ToUpper(c)
13330      int c;
13331 {
13332     return islower(c) ? toupper(c) : c;
13333 }
13334 #endif /* !_amigados    */
13335
13336 char *
13337 StrSave(s)
13338      char *s;
13339 {
13340     char *ret;
13341
13342     if ((ret = (char *) malloc(strlen(s) + 1))) {
13343         strcpy(ret, s);
13344     }
13345     return ret;
13346 }
13347
13348 char *
13349 StrSavePtr(s, savePtr)
13350      char *s, **savePtr;
13351 {
13352     if (*savePtr) {
13353         free(*savePtr);
13354     }
13355     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13356         strcpy(*savePtr, s);
13357     }
13358     return(*savePtr);
13359 }
13360
13361 char *
13362 PGNDate()
13363 {
13364     time_t clock;
13365     struct tm *tm;
13366     char buf[MSG_SIZ];
13367
13368     clock = time((time_t *)NULL);
13369     tm = localtime(&clock);
13370     sprintf(buf, "%04d.%02d.%02d",
13371             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13372     return StrSave(buf);
13373 }
13374
13375
13376 char *
13377 PositionToFEN(move, overrideCastling)
13378      int move;
13379      char *overrideCastling;
13380 {
13381     int i, j, fromX, fromY, toX, toY;
13382     int whiteToPlay;
13383     char buf[128];
13384     char *p, *q;
13385     int emptycount;
13386     ChessSquare piece;
13387
13388     whiteToPlay = (gameMode == EditPosition) ?
13389       !blackPlaysFirst : (move % 2 == 0);
13390     p = buf;
13391
13392     /* Piece placement data */
13393     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13394         emptycount = 0;
13395         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13396             if (boards[move][i][j] == EmptySquare) {
13397                 emptycount++;
13398             } else { ChessSquare piece = boards[move][i][j];
13399                 if (emptycount > 0) {
13400                     if(emptycount<10) /* [HGM] can be >= 10 */
13401                         *p++ = '0' + emptycount;
13402                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13403                     emptycount = 0;
13404                 }
13405                 if(PieceToChar(piece) == '+') {
13406                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13407                     *p++ = '+';
13408                     piece = (ChessSquare)(DEMOTED piece);
13409                 } 
13410                 *p++ = PieceToChar(piece);
13411                 if(p[-1] == '~') {
13412                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13413                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13414                     *p++ = '~';
13415                 }
13416             }
13417         }
13418         if (emptycount > 0) {
13419             if(emptycount<10) /* [HGM] can be >= 10 */
13420                 *p++ = '0' + emptycount;
13421             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13422             emptycount = 0;
13423         }
13424         *p++ = '/';
13425     }
13426     *(p - 1) = ' ';
13427
13428     /* [HGM] print Crazyhouse or Shogi holdings */
13429     if( gameInfo.holdingsWidth ) {
13430         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13431         q = p;
13432         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13433             piece = boards[move][i][BOARD_WIDTH-1];
13434             if( piece != EmptySquare )
13435               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13436                   *p++ = PieceToChar(piece);
13437         }
13438         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13439             piece = boards[move][BOARD_HEIGHT-i-1][0];
13440             if( piece != EmptySquare )
13441               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13442                   *p++ = PieceToChar(piece);
13443         }
13444
13445         if( q == p ) *p++ = '-';
13446         *p++ = ']';
13447         *p++ = ' ';
13448     }
13449
13450     /* Active color */
13451     *p++ = whiteToPlay ? 'w' : 'b';
13452     *p++ = ' ';
13453
13454   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13455     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13456   } else {
13457   if(nrCastlingRights) {
13458      q = p;
13459      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13460        /* [HGM] write directly from rights */
13461            if(castlingRights[move][2] >= 0 &&
13462               castlingRights[move][0] >= 0   )
13463                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13464            if(castlingRights[move][2] >= 0 &&
13465               castlingRights[move][1] >= 0   )
13466                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13467            if(castlingRights[move][5] >= 0 &&
13468               castlingRights[move][3] >= 0   )
13469                 *p++ = castlingRights[move][3] + AAA;
13470            if(castlingRights[move][5] >= 0 &&
13471               castlingRights[move][4] >= 0   )
13472                 *p++ = castlingRights[move][4] + AAA;
13473      } else {
13474
13475         /* [HGM] write true castling rights */
13476         if( nrCastlingRights == 6 ) {
13477             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13478                castlingRights[move][2] >= 0  ) *p++ = 'K';
13479             if(castlingRights[move][1] == BOARD_LEFT &&
13480                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13481             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13482                castlingRights[move][5] >= 0  ) *p++ = 'k';
13483             if(castlingRights[move][4] == BOARD_LEFT &&
13484                castlingRights[move][5] >= 0  ) *p++ = 'q';
13485         }
13486      }
13487      if (q == p) *p++ = '-'; /* No castling rights */
13488      *p++ = ' ';
13489   }
13490
13491   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13492      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13493     /* En passant target square */
13494     if (move > backwardMostMove) {
13495         fromX = moveList[move - 1][0] - AAA;
13496         fromY = moveList[move - 1][1] - ONE;
13497         toX = moveList[move - 1][2] - AAA;
13498         toY = moveList[move - 1][3] - ONE;
13499         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13500             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13501             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13502             fromX == toX) {
13503             /* 2-square pawn move just happened */
13504             *p++ = toX + AAA;
13505             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13506         } else {
13507             *p++ = '-';
13508         }
13509     } else if(move == backwardMostMove) {
13510         // [HGM] perhaps we should always do it like this, and forget the above?
13511         if(epStatus[move] >= 0) {
13512             *p++ = epStatus[move] + AAA;
13513             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13514         } else {
13515             *p++ = '-';
13516         }
13517     } else {
13518         *p++ = '-';
13519     }
13520     *p++ = ' ';
13521   }
13522   }
13523
13524     /* [HGM] find reversible plies */
13525     {   int i = 0, j=move;
13526
13527         if (appData.debugMode) { int k;
13528             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13529             for(k=backwardMostMove; k<=forwardMostMove; k++)
13530                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13531
13532         }
13533
13534         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13535         if( j == backwardMostMove ) i += initialRulePlies;
13536         sprintf(p, "%d ", i);
13537         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13538     }
13539     /* Fullmove number */
13540     sprintf(p, "%d", (move / 2) + 1);
13541     
13542     return StrSave(buf);
13543 }
13544
13545 Boolean
13546 ParseFEN(board, blackPlaysFirst, fen)
13547     Board board;
13548      int *blackPlaysFirst;
13549      char *fen;
13550 {
13551     int i, j;
13552     char *p;
13553     int emptycount;
13554     ChessSquare piece;
13555
13556     p = fen;
13557
13558     /* [HGM] by default clear Crazyhouse holdings, if present */
13559     if(gameInfo.holdingsWidth) {
13560        for(i=0; i<BOARD_HEIGHT; i++) {
13561            board[i][0]             = EmptySquare; /* black holdings */
13562            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13563            board[i][1]             = (ChessSquare) 0; /* black counts */
13564            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13565        }
13566     }
13567
13568     /* Piece placement data */
13569     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13570         j = 0;
13571         for (;;) {
13572             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13573                 if (*p == '/') p++;
13574                 emptycount = gameInfo.boardWidth - j;
13575                 while (emptycount--)
13576                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13577                 break;
13578 #if(BOARD_SIZE >= 10)
13579             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13580                 p++; emptycount=10;
13581                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13582                 while (emptycount--)
13583                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13584 #endif
13585             } else if (isdigit(*p)) {
13586                 emptycount = *p++ - '0';
13587                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13588                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13589                 while (emptycount--)
13590                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13591             } else if (*p == '+' || isalpha(*p)) {
13592                 if (j >= gameInfo.boardWidth) return FALSE;
13593                 if(*p=='+') {
13594                     piece = CharToPiece(*++p);
13595                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13596                     piece = (ChessSquare) (PROMOTED piece ); p++;
13597                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13598                 } else piece = CharToPiece(*p++);
13599
13600                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13601                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13602                     piece = (ChessSquare) (PROMOTED piece);
13603                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13604                     p++;
13605                 }
13606                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13607             } else {
13608                 return FALSE;
13609             }
13610         }
13611     }
13612     while (*p == '/' || *p == ' ') p++;
13613
13614     /* [HGM] look for Crazyhouse holdings here */
13615     while(*p==' ') p++;
13616     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13617         if(*p == '[') p++;
13618         if(*p == '-' ) *p++; /* empty holdings */ else {
13619             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13620             /* if we would allow FEN reading to set board size, we would   */
13621             /* have to add holdings and shift the board read so far here   */
13622             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13623                 *p++;
13624                 if((int) piece >= (int) BlackPawn ) {
13625                     i = (int)piece - (int)BlackPawn;
13626                     i = PieceToNumber((ChessSquare)i);
13627                     if( i >= gameInfo.holdingsSize ) return FALSE;
13628                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13629                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13630                 } else {
13631                     i = (int)piece - (int)WhitePawn;
13632                     i = PieceToNumber((ChessSquare)i);
13633                     if( i >= gameInfo.holdingsSize ) return FALSE;
13634                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13635                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13636                 }
13637             }
13638         }
13639         if(*p == ']') *p++;
13640     }
13641
13642     while(*p == ' ') p++;
13643
13644     /* Active color */
13645     switch (*p++) {
13646       case 'w':
13647         *blackPlaysFirst = FALSE;
13648         break;
13649       case 'b': 
13650         *blackPlaysFirst = TRUE;
13651         break;
13652       default:
13653         return FALSE;
13654     }
13655
13656     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13657     /* return the extra info in global variiables             */
13658
13659     /* set defaults in case FEN is incomplete */
13660     FENepStatus = EP_UNKNOWN;
13661     for(i=0; i<nrCastlingRights; i++ ) {
13662         FENcastlingRights[i] =
13663             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13664     }   /* assume possible unless obviously impossible */
13665     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13666     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13667     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13668     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13669     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13670     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13671     FENrulePlies = 0;
13672
13673     while(*p==' ') p++;
13674     if(nrCastlingRights) {
13675       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13676           /* castling indicator present, so default becomes no castlings */
13677           for(i=0; i<nrCastlingRights; i++ ) {
13678                  FENcastlingRights[i] = -1;
13679           }
13680       }
13681       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13682              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13683              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13684              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13685         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13686
13687         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13688             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13689             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13690         }
13691         switch(c) {
13692           case'K':
13693               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13694               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13695               FENcastlingRights[2] = whiteKingFile;
13696               break;
13697           case'Q':
13698               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13699               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13700               FENcastlingRights[2] = whiteKingFile;
13701               break;
13702           case'k':
13703               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13704               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13705               FENcastlingRights[5] = blackKingFile;
13706               break;
13707           case'q':
13708               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13709               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13710               FENcastlingRights[5] = blackKingFile;
13711           case '-':
13712               break;
13713           default: /* FRC castlings */
13714               if(c >= 'a') { /* black rights */
13715                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13716                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13717                   if(i == BOARD_RGHT) break;
13718                   FENcastlingRights[5] = i;
13719                   c -= AAA;
13720                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13721                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13722                   if(c > i)
13723                       FENcastlingRights[3] = c;
13724                   else
13725                       FENcastlingRights[4] = c;
13726               } else { /* white rights */
13727                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13728                     if(board[0][i] == WhiteKing) break;
13729                   if(i == BOARD_RGHT) break;
13730                   FENcastlingRights[2] = i;
13731                   c -= AAA - 'a' + 'A';
13732                   if(board[0][c] >= WhiteKing) break;
13733                   if(c > i)
13734                       FENcastlingRights[0] = c;
13735                   else
13736                       FENcastlingRights[1] = c;
13737               }
13738         }
13739       }
13740     if (appData.debugMode) {
13741         fprintf(debugFP, "FEN castling rights:");
13742         for(i=0; i<nrCastlingRights; i++)
13743         fprintf(debugFP, " %d", FENcastlingRights[i]);
13744         fprintf(debugFP, "\n");
13745     }
13746
13747       while(*p==' ') p++;
13748     }
13749
13750     /* read e.p. field in games that know e.p. capture */
13751     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13752        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13753       if(*p=='-') {
13754         p++; FENepStatus = EP_NONE;
13755       } else {
13756          char c = *p++ - AAA;
13757
13758          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13759          if(*p >= '0' && *p <='9') *p++;
13760          FENepStatus = c;
13761       }
13762     }
13763
13764
13765     if(sscanf(p, "%d", &i) == 1) {
13766         FENrulePlies = i; /* 50-move ply counter */
13767         /* (The move number is still ignored)    */
13768     }
13769
13770     return TRUE;
13771 }
13772       
13773 void
13774 EditPositionPasteFEN(char *fen)
13775 {
13776   if (fen != NULL) {
13777     Board initial_position;
13778
13779     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13780       DisplayError(_("Bad FEN position in clipboard"), 0);
13781       return ;
13782     } else {
13783       int savedBlackPlaysFirst = blackPlaysFirst;
13784       EditPositionEvent();
13785       blackPlaysFirst = savedBlackPlaysFirst;
13786       CopyBoard(boards[0], initial_position);
13787           /* [HGM] copy FEN attributes as well */
13788           {   int i;
13789               initialRulePlies = FENrulePlies;
13790               epStatus[0] = FENepStatus;
13791               for( i=0; i<nrCastlingRights; i++ )
13792                   castlingRights[0][i] = FENcastlingRights[i];
13793           }
13794       EditPositionDone();
13795       DisplayBothClocks();
13796       DrawPosition(FALSE, boards[currentMove]);
13797     }
13798   }
13799 }