Indicate squares a lifted piece can legally move to
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
239 int endPV = -1;
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
243 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
253
254 /* States for ics_getting_history */
255 #define H_FALSE 0
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
261
262 /* whosays values for GameEnds */
263 #define GE_ICS 0
264 #define GE_ENGINE 1
265 #define GE_PLAYER 2
266 #define GE_FILE 3
267 #define GE_XBOARD 4
268 #define GE_ENGINE1 5
269 #define GE_ENGINE2 6
270
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
273
274 /* Different types of move when calling RegisterMove */
275 #define CMAIL_MOVE   0
276 #define CMAIL_RESIGN 1
277 #define CMAIL_DRAW   2
278 #define CMAIL_ACCEPT 3
279
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
284
285 /* Telnet protocol constants */
286 #define TN_WILL 0373
287 #define TN_WONT 0374
288 #define TN_DO   0375
289 #define TN_DONT 0376
290 #define TN_IAC  0377
291 #define TN_ECHO 0001
292 #define TN_SGA  0003
293 #define TN_PORT 23
294
295 /* [AS] */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 {
298     assert( dst != NULL );
299     assert( src != NULL );
300     assert( count > 0 );
301
302     strncpy( dst, src, count );
303     dst[ count-1 ] = '\0';
304     return dst;
305 }
306
307 /* Some compiler can't cast u64 to double
308  * This function do the job for us:
309
310  * We use the highest bit for cast, this only
311  * works if the highest bit is not
312  * in use (This should not happen)
313  *
314  * We used this for all compiler
315  */
316 double
317 u64ToDouble(u64 value)
318 {
319   double r;
320   u64 tmp = value & u64Const(0x7fffffffffffffff);
321   r = (double)(s64)tmp;
322   if (value & u64Const(0x8000000000000000))
323        r +=  9.2233720368547758080e18; /* 2^63 */
324  return r;
325 }
326
327 /* Fake up flags for now, as we aren't keeping track of castling
328    availability yet. [HGM] Change of logic: the flag now only
329    indicates the type of castlings allowed by the rule of the game.
330    The actual rights themselves are maintained in the array
331    castlingRights, as part of the game history, and are not probed
332    by this function.
333  */
334 int
335 PosFlags(index)
336 {
337   int flags = F_ALL_CASTLE_OK;
338   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339   switch (gameInfo.variant) {
340   case VariantSuicide:
341     flags &= ~F_ALL_CASTLE_OK;
342   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343     flags |= F_IGNORE_CHECK;
344   case VariantLosers:
345     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346     break;
347   case VariantAtomic:
348     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349     break;
350   case VariantKriegspiel:
351     flags |= F_KRIEGSPIEL_CAPTURE;
352     break;
353   case VariantCapaRandom: 
354   case VariantFischeRandom:
355     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356   case VariantNoCastle:
357   case VariantShatranj:
358   case VariantCourier:
359     flags &= ~F_ALL_CASTLE_OK;
360     break;
361   default:
362     break;
363   }
364   return flags;
365 }
366
367 FILE *gameFileFP, *debugFP;
368
369 /* 
370     [AS] Note: sometimes, the sscanf() function is used to parse the input
371     into a fixed-size buffer. Because of this, we must be prepared to
372     receive strings as long as the size of the input buffer, which is currently
373     set to 4K for Windows and 8K for the rest.
374     So, we must either allocate sufficiently large buffers here, or
375     reduce the size of the input buffer in the input reading part.
376 */
377
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
381
382 ChessProgramState first, second;
383
384 /* premove variables */
385 int premoveToX = 0;
386 int premoveToY = 0;
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
390 int gotPremove = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
393
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
396
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
424 int movesPerSession;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
429 int matchGame = 0;
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
433
434 /* animateTraining preserves the state of appData.animate
435  * when Training mode is activated. This allows the
436  * response to be animated when appData.animate == TRUE and
437  * appData.animateDragging == TRUE.
438  */
439 Boolean animateTraining;
440
441 GameInfo gameInfo;
442
443 AppData appData;
444
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
448 signed char  initialRights[BOARD_FILES];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 // [HGM] vari: next 12 to save and restore variations
457 #define MAX_VARIATIONS 10
458 int framePtr = MAX_MOVES-1; // points to free stack entry
459 int storedGames = 0;
460 int savedFirst[MAX_VARIATIONS];
461 int savedLast[MAX_VARIATIONS];
462 int savedFramePtr[MAX_VARIATIONS];
463 char *savedDetails[MAX_VARIATIONS];
464 ChessMove savedResult[MAX_VARIATIONS];
465
466 void PushTail P((int firstMove, int lastMove));
467 Boolean PopTail P((Boolean annotate));
468 void CleanupTail P((void));
469
470 ChessSquare  FIDEArray[2][BOARD_FILES] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_FILES] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_FILES>=10)
507 ChessSquare ShogiArray[2][BOARD_FILES] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_FILES] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_FILES>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_FILES>=10)
570
571 #if (BOARD_FILES>=12)
572 ChessSquare CourierArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_FILES>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_FILES>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY       
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<=framePtr; i++ ) {
662             pvInfoList[i].depth = -1;
663             boards[i][EP_STATUS] = EP_NONE;
664             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696     
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
813         appData.clockMode = FALSE;
814         first.sendTime = second.sendTime = 0;
815     }
816     
817 #if ZIPPY
818     /* Override some settings from environment variables, for backward
819        compatibility.  Unfortunately it's not feasible to have the env
820        vars just set defaults, at least in xboard.  Ugh.
821     */
822     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
823       ZippyInit();
824     }
825 #endif
826     
827     if (appData.noChessProgram) {
828         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829         sprintf(programVersion, "%s", PACKAGE_STRING);
830     } else {
831       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
832       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
833       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
834     }
835
836     if (!appData.icsActive) {
837       char buf[MSG_SIZ];
838       /* Check for variants that are supported only in ICS mode,
839          or not at all.  Some that are accepted here nevertheless
840          have bugs; see comments below.
841       */
842       VariantClass variant = StringToVariant(appData.variant);
843       switch (variant) {
844       case VariantBughouse:     /* need four players and two boards */
845       case VariantKriegspiel:   /* need to hide pieces and move details */
846       /* case VariantFischeRandom: (Fabien: moved below) */
847         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantUnknown:
852       case VariantLoadable:
853       case Variant29:
854       case Variant30:
855       case Variant31:
856       case Variant32:
857       case Variant33:
858       case Variant34:
859       case Variant35:
860       case Variant36:
861       default:
862         sprintf(buf, _("Unknown variant name %s"), appData.variant);
863         DisplayFatalError(buf, 0, 2);
864         return;
865
866       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
867       case VariantFairy:      /* [HGM] TestLegality definitely off! */
868       case VariantGothic:     /* [HGM] should work */
869       case VariantCapablanca: /* [HGM] should work */
870       case VariantCourier:    /* [HGM] initial forced moves not implemented */
871       case VariantShogi:      /* [HGM] drops not tested for legality */
872       case VariantKnightmate: /* [HGM] should work */
873       case VariantCylinder:   /* [HGM] untested */
874       case VariantFalcon:     /* [HGM] untested */
875       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
876                                  offboard interposition not understood */
877       case VariantNormal:     /* definitely works! */
878       case VariantWildCastle: /* pieces not automatically shuffled */
879       case VariantNoCastle:   /* pieces not automatically shuffled */
880       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
881       case VariantLosers:     /* should work except for win condition,
882                                  and doesn't know captures are mandatory */
883       case VariantSuicide:    /* should work except for win condition,
884                                  and doesn't know captures are mandatory */
885       case VariantGiveaway:   /* should work except for win condition,
886                                  and doesn't know captures are mandatory */
887       case VariantTwoKings:   /* should work */
888       case VariantAtomic:     /* should work except for win condition */
889       case Variant3Check:     /* should work except for win condition */
890       case VariantShatranj:   /* should work except for all win conditions */
891       case VariantBerolina:   /* might work if TestLegality is off */
892       case VariantCapaRandom: /* should work */
893       case VariantJanus:      /* should work */
894       case VariantSuper:      /* experimental */
895       case VariantGreat:      /* experimental, requires legality testing to be off */
896         break;
897       }
898     }
899
900     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
901     InitEngineUCI( installDir, &second );
902 }
903
904 int NextIntegerFromString( char ** str, long * value )
905 {
906     int result = -1;
907     char * s = *str;
908
909     while( *s == ' ' || *s == '\t' ) {
910         s++;
911     }
912
913     *value = 0;
914
915     if( *s >= '0' && *s <= '9' ) {
916         while( *s >= '0' && *s <= '9' ) {
917             *value = *value * 10 + (*s - '0');
918             s++;
919         }
920
921         result = 0;
922     }
923
924     *str = s;
925
926     return result;
927 }
928
929 int NextTimeControlFromString( char ** str, long * value )
930 {
931     long temp;
932     int result = NextIntegerFromString( str, &temp );
933
934     if( result == 0 ) {
935         *value = temp * 60; /* Minutes */
936         if( **str == ':' ) {
937             (*str)++;
938             result = NextIntegerFromString( str, &temp );
939             *value += temp; /* Seconds */
940         }
941     }
942
943     return result;
944 }
945
946 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
947 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
948     int result = -1; long temp, temp2;
949
950     if(**str != '+') return -1; // old params remain in force!
951     (*str)++;
952     if( NextTimeControlFromString( str, &temp ) ) return -1;
953
954     if(**str != '/') {
955         /* time only: incremental or sudden-death time control */
956         if(**str == '+') { /* increment follows; read it */
957             (*str)++;
958             if(result = NextIntegerFromString( str, &temp2)) return -1;
959             *inc = temp2 * 1000;
960         } else *inc = 0;
961         *moves = 0; *tc = temp * 1000; 
962         return 0;
963     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
964
965     (*str)++; /* classical time control */
966     result = NextTimeControlFromString( str, &temp2);
967     if(result == 0) {
968         *moves = temp/60;
969         *tc    = temp2 * 1000;
970         *inc   = 0;
971     }
972     return result;
973 }
974
975 int GetTimeQuota(int movenr)
976 {   /* [HGM] get time to add from the multi-session time-control string */
977     int moves=1; /* kludge to force reading of first session */
978     long time, increment;
979     char *s = fullTimeControlString;
980
981     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
982     do {
983         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
984         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
985         if(movenr == -1) return time;    /* last move before new session     */
986         if(!moves) return increment;     /* current session is incremental   */
987         if(movenr >= 0) movenr -= moves; /* we already finished this session */
988     } while(movenr >= -1);               /* try again for next session       */
989
990     return 0; // no new time quota on this move
991 }
992
993 int
994 ParseTimeControl(tc, ti, mps)
995      char *tc;
996      int ti;
997      int mps;
998 {
999   long tc1;
1000   long tc2;
1001   char buf[MSG_SIZ];
1002   
1003   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1004   if(ti > 0) {
1005     if(mps)
1006       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1007     else sprintf(buf, "+%s+%d", tc, ti);
1008   } else {
1009     if(mps)
1010              sprintf(buf, "+%d/%s", mps, tc);
1011     else sprintf(buf, "+%s", tc);
1012   }
1013   fullTimeControlString = StrSave(buf);
1014   
1015   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1016     return FALSE;
1017   }
1018   
1019   if( *tc == '/' ) {
1020     /* Parse second time control */
1021     tc++;
1022     
1023     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1024       return FALSE;
1025     }
1026     
1027     if( tc2 == 0 ) {
1028       return FALSE;
1029     }
1030     
1031     timeControl_2 = tc2 * 1000;
1032   }
1033   else {
1034     timeControl_2 = 0;
1035   }
1036   
1037   if( tc1 == 0 ) {
1038     return FALSE;
1039   }
1040   
1041   timeControl = tc1 * 1000;
1042   
1043   if (ti >= 0) {
1044     timeIncrement = ti * 1000;  /* convert to ms */
1045     movesPerSession = 0;
1046   } else {
1047     timeIncrement = 0;
1048     movesPerSession = mps;
1049   }
1050   return TRUE;
1051 }
1052
1053 void
1054 InitBackEnd2()
1055 {
1056     if (appData.debugMode) {
1057         fprintf(debugFP, "%s\n", programVersion);
1058     }
1059
1060     set_cont_sequence(appData.wrapContSeq);
1061     if (appData.matchGames > 0) {
1062         appData.matchMode = TRUE;
1063     } else if (appData.matchMode) {
1064         appData.matchGames = 1;
1065     }
1066     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1067         appData.matchGames = appData.sameColorGames;
1068     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1069         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1070         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1071     }
1072     Reset(TRUE, FALSE);
1073     if (appData.noChessProgram || first.protocolVersion == 1) {
1074       InitBackEnd3();
1075     } else {
1076       /* kludge: allow timeout for initial "feature" commands */
1077       FreezeUI();
1078       DisplayMessage("", _("Starting chess program"));
1079       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1080     }
1081 }
1082
1083 void
1084 InitBackEnd3 P((void))
1085 {
1086     GameMode initialMode;
1087     char buf[MSG_SIZ];
1088     int err;
1089
1090     InitChessProgram(&first, startedFromSetupPosition);
1091
1092
1093     if (appData.icsActive) {
1094 #ifdef WIN32
1095         /* [DM] Make a console window if needed [HGM] merged ifs */
1096         ConsoleCreate(); 
1097 #endif
1098         err = establish();
1099         if (err != 0) {
1100             if (*appData.icsCommPort != NULLCHAR) {
1101                 sprintf(buf, _("Could not open comm port %s"),  
1102                         appData.icsCommPort);
1103             } else {
1104                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1105                         appData.icsHost, appData.icsPort);
1106             }
1107             DisplayFatalError(buf, err, 1);
1108             return;
1109         }
1110         SetICSMode();
1111         telnetISR =
1112           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1113         fromUserISR =
1114           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1115     } else if (appData.noChessProgram) {
1116         SetNCPMode();
1117     } else {
1118         SetGNUMode();
1119     }
1120
1121     if (*appData.cmailGameName != NULLCHAR) {
1122         SetCmailMode();
1123         OpenLoopback(&cmailPR);
1124         cmailISR =
1125           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126     }
1127     
1128     ThawUI();
1129     DisplayMessage("", "");
1130     if (StrCaseCmp(appData.initialMode, "") == 0) {
1131       initialMode = BeginningOfGame;
1132     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1133       initialMode = TwoMachinesPlay;
1134     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1135       initialMode = AnalyzeFile; 
1136     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1137       initialMode = AnalyzeMode;
1138     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1139       initialMode = MachinePlaysWhite;
1140     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1141       initialMode = MachinePlaysBlack;
1142     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1143       initialMode = EditGame;
1144     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1145       initialMode = EditPosition;
1146     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1147       initialMode = Training;
1148     } else {
1149       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1150       DisplayFatalError(buf, 0, 2);
1151       return;
1152     }
1153
1154     if (appData.matchMode) {
1155         /* Set up machine vs. machine match */
1156         if (appData.noChessProgram) {
1157             DisplayFatalError(_("Can't have a match with no chess programs"),
1158                               0, 2);
1159             return;
1160         }
1161         matchMode = TRUE;
1162         matchGame = 1;
1163         if (*appData.loadGameFile != NULLCHAR) {
1164             int index = appData.loadGameIndex; // [HGM] autoinc
1165             if(index<0) lastIndex = index = 1;
1166             if (!LoadGameFromFile(appData.loadGameFile,
1167                                   index,
1168                                   appData.loadGameFile, FALSE)) {
1169                 DisplayFatalError(_("Bad game file"), 0, 1);
1170                 return;
1171             }
1172         } else if (*appData.loadPositionFile != NULLCHAR) {
1173             int index = appData.loadPositionIndex; // [HGM] autoinc
1174             if(index<0) lastIndex = index = 1;
1175             if (!LoadPositionFromFile(appData.loadPositionFile,
1176                                       index,
1177                                       appData.loadPositionFile)) {
1178                 DisplayFatalError(_("Bad position file"), 0, 1);
1179                 return;
1180             }
1181         }
1182         TwoMachinesEvent();
1183     } else if (*appData.cmailGameName != NULLCHAR) {
1184         /* Set up cmail mode */
1185         ReloadCmailMsgEvent(TRUE);
1186     } else {
1187         /* Set up other modes */
1188         if (initialMode == AnalyzeFile) {
1189           if (*appData.loadGameFile == NULLCHAR) {
1190             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191             return;
1192           }
1193         }
1194         if (*appData.loadGameFile != NULLCHAR) {
1195             (void) LoadGameFromFile(appData.loadGameFile,
1196                                     appData.loadGameIndex,
1197                                     appData.loadGameFile, TRUE);
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             (void) LoadPositionFromFile(appData.loadPositionFile,
1200                                         appData.loadPositionIndex,
1201                                         appData.loadPositionFile);
1202             /* [HGM] try to make self-starting even after FEN load */
1203             /* to allow automatic setup of fairy variants with wtm */
1204             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1205                 gameMode = BeginningOfGame;
1206                 setboardSpoiledMachineBlack = 1;
1207             }
1208             /* [HGM] loadPos: make that every new game uses the setup */
1209             /* from file as long as we do not switch variant          */
1210             if(!blackPlaysFirst) {
1211                 startedFromPositionFile = TRUE;
1212                 CopyBoard(filePosition, boards[0]);
1213             }
1214         }
1215         if (initialMode == AnalyzeMode) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1218             return;
1219           }
1220           if (appData.icsActive) {
1221             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222             return;
1223           }
1224           AnalyzeModeEvent();
1225         } else if (initialMode == AnalyzeFile) {
1226           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1227           ShowThinkingEvent();
1228           AnalyzeFileEvent();
1229           AnalysisPeriodicEvent(1);
1230         } else if (initialMode == MachinePlaysWhite) {
1231           if (appData.noChessProgram) {
1232             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233                               0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238                               0, 2);
1239             return;
1240           }
1241           MachineWhiteEvent();
1242         } else if (initialMode == MachinePlaysBlack) {
1243           if (appData.noChessProgram) {
1244             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245                               0, 2);
1246             return;
1247           }
1248           if (appData.icsActive) {
1249             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250                               0, 2);
1251             return;
1252           }
1253           MachineBlackEvent();
1254         } else if (initialMode == TwoMachinesPlay) {
1255           if (appData.noChessProgram) {
1256             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257                               0, 2);
1258             return;
1259           }
1260           if (appData.icsActive) {
1261             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1262                               0, 2);
1263             return;
1264           }
1265           TwoMachinesEvent();
1266         } else if (initialMode == EditGame) {
1267           EditGameEvent();
1268         } else if (initialMode == EditPosition) {
1269           EditPositionEvent();
1270         } else if (initialMode == Training) {
1271           if (*appData.loadGameFile == NULLCHAR) {
1272             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1273             return;
1274           }
1275           TrainingEvent();
1276         }
1277     }
1278 }
1279
1280 /*
1281  * Establish will establish a contact to a remote host.port.
1282  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1283  *  used to talk to the host.
1284  * Returns 0 if okay, error code if not.
1285  */
1286 int
1287 establish()
1288 {
1289     char buf[MSG_SIZ];
1290
1291     if (*appData.icsCommPort != NULLCHAR) {
1292         /* Talk to the host through a serial comm port */
1293         return OpenCommPort(appData.icsCommPort, &icsPR);
1294
1295     } else if (*appData.gateway != NULLCHAR) {
1296         if (*appData.remoteShell == NULLCHAR) {
1297             /* Use the rcmd protocol to run telnet program on a gateway host */
1298             snprintf(buf, sizeof(buf), "%s %s %s",
1299                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1300             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1301
1302         } else {
1303             /* Use the rsh program to run telnet program on a gateway host */
1304             if (*appData.remoteUser == NULLCHAR) {
1305                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1306                         appData.gateway, appData.telnetProgram,
1307                         appData.icsHost, appData.icsPort);
1308             } else {
1309                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1310                         appData.remoteShell, appData.gateway, 
1311                         appData.remoteUser, appData.telnetProgram,
1312                         appData.icsHost, appData.icsPort);
1313             }
1314             return StartChildProcess(buf, "", &icsPR);
1315
1316         }
1317     } else if (appData.useTelnet) {
1318         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1319
1320     } else {
1321         /* TCP socket interface differs somewhat between
1322            Unix and NT; handle details in the front end.
1323            */
1324         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1325     }
1326 }
1327
1328 void
1329 show_bytes(fp, buf, count)
1330      FILE *fp;
1331      char *buf;
1332      int count;
1333 {
1334     while (count--) {
1335         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1336             fprintf(fp, "\\%03o", *buf & 0xff);
1337         } else {
1338             putc(*buf, fp);
1339         }
1340         buf++;
1341     }
1342     fflush(fp);
1343 }
1344
1345 /* Returns an errno value */
1346 int
1347 OutputMaybeTelnet(pr, message, count, outError)
1348      ProcRef pr;
1349      char *message;
1350      int count;
1351      int *outError;
1352 {
1353     char buf[8192], *p, *q, *buflim;
1354     int left, newcount, outcount;
1355
1356     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1357         *appData.gateway != NULLCHAR) {
1358         if (appData.debugMode) {
1359             fprintf(debugFP, ">ICS: ");
1360             show_bytes(debugFP, message, count);
1361             fprintf(debugFP, "\n");
1362         }
1363         return OutputToProcess(pr, message, count, outError);
1364     }
1365
1366     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1367     p = message;
1368     q = buf;
1369     left = count;
1370     newcount = 0;
1371     while (left) {
1372         if (q >= buflim) {
1373             if (appData.debugMode) {
1374                 fprintf(debugFP, ">ICS: ");
1375                 show_bytes(debugFP, buf, newcount);
1376                 fprintf(debugFP, "\n");
1377             }
1378             outcount = OutputToProcess(pr, buf, newcount, outError);
1379             if (outcount < newcount) return -1; /* to be sure */
1380             q = buf;
1381             newcount = 0;
1382         }
1383         if (*p == '\n') {
1384             *q++ = '\r';
1385             newcount++;
1386         } else if (((unsigned char) *p) == TN_IAC) {
1387             *q++ = (char) TN_IAC;
1388             newcount ++;
1389         }
1390         *q++ = *p++;
1391         newcount++;
1392         left--;
1393     }
1394     if (appData.debugMode) {
1395         fprintf(debugFP, ">ICS: ");
1396         show_bytes(debugFP, buf, newcount);
1397         fprintf(debugFP, "\n");
1398     }
1399     outcount = OutputToProcess(pr, buf, newcount, outError);
1400     if (outcount < newcount) return -1; /* to be sure */
1401     return count;
1402 }
1403
1404 void
1405 read_from_player(isr, closure, message, count, error)
1406      InputSourceRef isr;
1407      VOIDSTAR closure;
1408      char *message;
1409      int count;
1410      int error;
1411 {
1412     int outError, outCount;
1413     static int gotEof = 0;
1414
1415     /* Pass data read from player on to ICS */
1416     if (count > 0) {
1417         gotEof = 0;
1418         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1419         if (outCount < count) {
1420             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1421         }
1422     } else if (count < 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1425     } else if (gotEof++ > 0) {
1426         RemoveInputSource(isr);
1427         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1428     }
1429 }
1430
1431 void
1432 KeepAlive()
1433 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1434     SendToICS("date\n");
1435     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1436 }
1437
1438 /* added routine for printf style output to ics */
1439 void ics_printf(char *format, ...)
1440 {
1441     char buffer[MSG_SIZ];
1442     va_list args;
1443
1444     va_start(args, format);
1445     vsnprintf(buffer, sizeof(buffer), format, args);
1446     buffer[sizeof(buffer)-1] = '\0';
1447     SendToICS(buffer);
1448     va_end(args);
1449 }
1450
1451 void
1452 SendToICS(s)
1453      char *s;
1454 {
1455     int count, outCount, outError;
1456
1457     if (icsPR == NULL) return;
1458
1459     count = strlen(s);
1460     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461     if (outCount < count) {
1462         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463     }
1464 }
1465
1466 /* This is used for sending logon scripts to the ICS. Sending
1467    without a delay causes problems when using timestamp on ICC
1468    (at least on my machine). */
1469 void
1470 SendToICSDelayed(s,msdelay)
1471      char *s;
1472      long msdelay;
1473 {
1474     int count, outCount, outError;
1475
1476     if (icsPR == NULL) return;
1477
1478     count = strlen(s);
1479     if (appData.debugMode) {
1480         fprintf(debugFP, ">ICS: ");
1481         show_bytes(debugFP, s, count);
1482         fprintf(debugFP, "\n");
1483     }
1484     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1485                                       msdelay);
1486     if (outCount < count) {
1487         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1488     }
1489 }
1490
1491
1492 /* Remove all highlighting escape sequences in s
1493    Also deletes any suffix starting with '(' 
1494    */
1495 char *
1496 StripHighlightAndTitle(s)
1497      char *s;
1498 {
1499     static char retbuf[MSG_SIZ];
1500     char *p = retbuf;
1501
1502     while (*s != NULLCHAR) {
1503         while (*s == '\033') {
1504             while (*s != NULLCHAR && !isalpha(*s)) s++;
1505             if (*s != NULLCHAR) s++;
1506         }
1507         while (*s != NULLCHAR && *s != '\033') {
1508             if (*s == '(' || *s == '[') {
1509                 *p = NULLCHAR;
1510                 return retbuf;
1511             }
1512             *p++ = *s++;
1513         }
1514     }
1515     *p = NULLCHAR;
1516     return retbuf;
1517 }
1518
1519 /* Remove all highlighting escape sequences in s */
1520 char *
1521 StripHighlight(s)
1522      char *s;
1523 {
1524     static char retbuf[MSG_SIZ];
1525     char *p = retbuf;
1526
1527     while (*s != NULLCHAR) {
1528         while (*s == '\033') {
1529             while (*s != NULLCHAR && !isalpha(*s)) s++;
1530             if (*s != NULLCHAR) s++;
1531         }
1532         while (*s != NULLCHAR && *s != '\033') {
1533             *p++ = *s++;
1534         }
1535     }
1536     *p = NULLCHAR;
1537     return retbuf;
1538 }
1539
1540 char *variantNames[] = VARIANT_NAMES;
1541 char *
1542 VariantName(v)
1543      VariantClass v;
1544 {
1545     return variantNames[v];
1546 }
1547
1548
1549 /* Identify a variant from the strings the chess servers use or the
1550    PGN Variant tag names we use. */
1551 VariantClass
1552 StringToVariant(e)
1553      char *e;
1554 {
1555     char *p;
1556     int wnum = -1;
1557     VariantClass v = VariantNormal;
1558     int i, found = FALSE;
1559     char buf[MSG_SIZ];
1560
1561     if (!e) return v;
1562
1563     /* [HGM] skip over optional board-size prefixes */
1564     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566         while( *e++ != '_');
1567     }
1568
1569     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570         v = VariantNormal;
1571         found = TRUE;
1572     } else
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574       if (StrCaseStr(e, variantNames[i])) {
1575         v = (VariantClass) i;
1576         found = TRUE;
1577         break;
1578       }
1579     }
1580
1581     if (!found) {
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583           || StrCaseStr(e, "wild/fr") 
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585         v = VariantFischeRandom;
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587                  (i = 1, p = StrCaseStr(e, "w"))) {
1588         p += i;
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1590         if (isdigit(*p)) {
1591           wnum = atoi(p);
1592         } else {
1593           wnum = -1;
1594         }
1595         switch (wnum) {
1596         case 0: /* FICS only, actually */
1597         case 1:
1598           /* Castling legal even if K starts on d-file */
1599           v = VariantWildCastle;
1600           break;
1601         case 2:
1602         case 3:
1603         case 4:
1604           /* Castling illegal even if K & R happen to start in
1605              normal positions. */
1606           v = VariantNoCastle;
1607           break;
1608         case 5:
1609         case 7:
1610         case 8:
1611         case 10:
1612         case 11:
1613         case 12:
1614         case 13:
1615         case 14:
1616         case 15:
1617         case 18:
1618         case 19:
1619           /* Castling legal iff K & R start in normal positions */
1620           v = VariantNormal;
1621           break;
1622         case 6:
1623         case 20:
1624         case 21:
1625           /* Special wilds for position setup; unclear what to do here */
1626           v = VariantLoadable;
1627           break;
1628         case 9:
1629           /* Bizarre ICC game */
1630           v = VariantTwoKings;
1631           break;
1632         case 16:
1633           v = VariantKriegspiel;
1634           break;
1635         case 17:
1636           v = VariantLosers;
1637           break;
1638         case 22:
1639           v = VariantFischeRandom;
1640           break;
1641         case 23:
1642           v = VariantCrazyhouse;
1643           break;
1644         case 24:
1645           v = VariantBughouse;
1646           break;
1647         case 25:
1648           v = Variant3Check;
1649           break;
1650         case 26:
1651           /* Not quite the same as FICS suicide! */
1652           v = VariantGiveaway;
1653           break;
1654         case 27:
1655           v = VariantAtomic;
1656           break;
1657         case 28:
1658           v = VariantShatranj;
1659           break;
1660
1661         /* Temporary names for future ICC types.  The name *will* change in 
1662            the next xboard/WinBoard release after ICC defines it. */
1663         case 29:
1664           v = Variant29;
1665           break;
1666         case 30:
1667           v = Variant30;
1668           break;
1669         case 31:
1670           v = Variant31;
1671           break;
1672         case 32:
1673           v = Variant32;
1674           break;
1675         case 33:
1676           v = Variant33;
1677           break;
1678         case 34:
1679           v = Variant34;
1680           break;
1681         case 35:
1682           v = Variant35;
1683           break;
1684         case 36:
1685           v = Variant36;
1686           break;
1687         case 37:
1688           v = VariantShogi;
1689           break;
1690         case 38:
1691           v = VariantXiangqi;
1692           break;
1693         case 39:
1694           v = VariantCourier;
1695           break;
1696         case 40:
1697           v = VariantGothic;
1698           break;
1699         case 41:
1700           v = VariantCapablanca;
1701           break;
1702         case 42:
1703           v = VariantKnightmate;
1704           break;
1705         case 43:
1706           v = VariantFairy;
1707           break;
1708         case 44:
1709           v = VariantCylinder;
1710           break;
1711         case 45:
1712           v = VariantFalcon;
1713           break;
1714         case 46:
1715           v = VariantCapaRandom;
1716           break;
1717         case 47:
1718           v = VariantBerolina;
1719           break;
1720         case 48:
1721           v = VariantJanus;
1722           break;
1723         case 49:
1724           v = VariantSuper;
1725           break;
1726         case 50:
1727           v = VariantGreat;
1728           break;
1729         case -1:
1730           /* Found "wild" or "w" in the string but no number;
1731              must assume it's normal chess. */
1732           v = VariantNormal;
1733           break;
1734         default:
1735           sprintf(buf, _("Unknown wild type %d"), wnum);
1736           DisplayError(buf, 0);
1737           v = VariantUnknown;
1738           break;
1739         }
1740       }
1741     }
1742     if (appData.debugMode) {
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744               e, wnum, VariantName(v));
1745     }
1746     return v;
1747 }
1748
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1751
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753    advance *index beyond it, and set leftover_start to the new value of
1754    *index; else return FALSE.  If pattern contains the character '*', it
1755    matches any sequence of characters not containing '\r', '\n', or the
1756    character following the '*' (if any), and the matched sequence(s) are
1757    copied into star_match.
1758    */
1759 int
1760 looking_at(buf, index, pattern)
1761      char *buf;
1762      int *index;
1763      char *pattern;
1764 {
1765     char *bufp = &buf[*index], *patternp = pattern;
1766     int star_count = 0;
1767     char *matchp = star_match[0];
1768     
1769     for (;;) {
1770         if (*patternp == NULLCHAR) {
1771             *index = leftover_start = bufp - buf;
1772             *matchp = NULLCHAR;
1773             return TRUE;
1774         }
1775         if (*bufp == NULLCHAR) return FALSE;
1776         if (*patternp == '*') {
1777             if (*bufp == *(patternp + 1)) {
1778                 *matchp = NULLCHAR;
1779                 matchp = star_match[++star_count];
1780                 patternp += 2;
1781                 bufp++;
1782                 continue;
1783             } else if (*bufp == '\n' || *bufp == '\r') {
1784                 patternp++;
1785                 if (*patternp == NULLCHAR)
1786                   continue;
1787                 else
1788                   return FALSE;
1789             } else {
1790                 *matchp++ = *bufp++;
1791                 continue;
1792             }
1793         }
1794         if (*patternp != *bufp) return FALSE;
1795         patternp++;
1796         bufp++;
1797     }
1798 }
1799
1800 void
1801 SendToPlayer(data, length)
1802      char *data;
1803      int length;
1804 {
1805     int error, outCount;
1806     outCount = OutputToProcess(NoProc, data, length, &error);
1807     if (outCount < length) {
1808         DisplayFatalError(_("Error writing to display"), error, 1);
1809     }
1810 }
1811
1812 void
1813 PackHolding(packed, holding)
1814      char packed[];
1815      char *holding;
1816 {
1817     char *p = holding;
1818     char *q = packed;
1819     int runlength = 0;
1820     int curr = 9999;
1821     do {
1822         if (*p == curr) {
1823             runlength++;
1824         } else {
1825             switch (runlength) {
1826               case 0:
1827                 break;
1828               case 1:
1829                 *q++ = curr;
1830                 break;
1831               case 2:
1832                 *q++ = curr;
1833                 *q++ = curr;
1834                 break;
1835               default:
1836                 sprintf(q, "%d", runlength);
1837                 while (*q) q++;
1838                 *q++ = curr;
1839                 break;
1840             }
1841             runlength = 1;
1842             curr = *p;
1843         }
1844     } while (*p++);
1845     *q = NULLCHAR;
1846 }
1847
1848 /* Telnet protocol requests from the front end */
1849 void
1850 TelnetRequest(ddww, option)
1851      unsigned char ddww, option;
1852 {
1853     unsigned char msg[3];
1854     int outCount, outError;
1855
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857
1858     if (appData.debugMode) {
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860         switch (ddww) {
1861           case TN_DO:
1862             ddwwStr = "DO";
1863             break;
1864           case TN_DONT:
1865             ddwwStr = "DONT";
1866             break;
1867           case TN_WILL:
1868             ddwwStr = "WILL";
1869             break;
1870           case TN_WONT:
1871             ddwwStr = "WONT";
1872             break;
1873           default:
1874             ddwwStr = buf1;
1875             sprintf(buf1, "%d", ddww);
1876             break;
1877         }
1878         switch (option) {
1879           case TN_ECHO:
1880             optionStr = "ECHO";
1881             break;
1882           default:
1883             optionStr = buf2;
1884             sprintf(buf2, "%d", option);
1885             break;
1886         }
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1888     }
1889     msg[0] = TN_IAC;
1890     msg[1] = ddww;
1891     msg[2] = option;
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893     if (outCount < 3) {
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1895     }
1896 }
1897
1898 void
1899 DoEcho()
1900 {
1901     if (!appData.icsActive) return;
1902     TelnetRequest(TN_DO, TN_ECHO);
1903 }
1904
1905 void
1906 DontEcho()
1907 {
1908     if (!appData.icsActive) return;
1909     TelnetRequest(TN_DONT, TN_ECHO);
1910 }
1911
1912 void
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 {
1915     /* put the holdings sent to us by the server on the board holdings area */
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917     char p;
1918     ChessSquare piece;
1919
1920     if(gameInfo.holdingsWidth < 2)  return;
1921     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1922         return; // prevent overwriting by pre-board holdings
1923
1924     if( (int)lowestPiece >= BlackPawn ) {
1925         holdingsColumn = 0;
1926         countsColumn = 1;
1927         holdingsStartRow = BOARD_HEIGHT-1;
1928         direction = -1;
1929     } else {
1930         holdingsColumn = BOARD_WIDTH-1;
1931         countsColumn = BOARD_WIDTH-2;
1932         holdingsStartRow = 0;
1933         direction = 1;
1934     }
1935
1936     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1937         board[i][holdingsColumn] = EmptySquare;
1938         board[i][countsColumn]   = (ChessSquare) 0;
1939     }
1940     while( (p=*holdings++) != NULLCHAR ) {
1941         piece = CharToPiece( ToUpper(p) );
1942         if(piece == EmptySquare) continue;
1943         /*j = (int) piece - (int) WhitePawn;*/
1944         j = PieceToNumber(piece);
1945         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1946         if(j < 0) continue;               /* should not happen */
1947         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1948         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1949         board[holdingsStartRow+j*direction][countsColumn]++;
1950     }
1951 }
1952
1953
1954 void
1955 VariantSwitch(Board board, VariantClass newVariant)
1956 {
1957    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1958    Board oldBoard;
1959
1960    startedFromPositionFile = FALSE;
1961    if(gameInfo.variant == newVariant) return;
1962
1963    /* [HGM] This routine is called each time an assignment is made to
1964     * gameInfo.variant during a game, to make sure the board sizes
1965     * are set to match the new variant. If that means adding or deleting
1966     * holdings, we shift the playing board accordingly
1967     * This kludge is needed because in ICS observe mode, we get boards
1968     * of an ongoing game without knowing the variant, and learn about the
1969     * latter only later. This can be because of the move list we requested,
1970     * in which case the game history is refilled from the beginning anyway,
1971     * but also when receiving holdings of a crazyhouse game. In the latter
1972     * case we want to add those holdings to the already received position.
1973     */
1974
1975    
1976    if (appData.debugMode) {
1977      fprintf(debugFP, "Switch board from %s to %s\n",
1978              VariantName(gameInfo.variant), VariantName(newVariant));
1979      setbuf(debugFP, NULL);
1980    }
1981    shuffleOpenings = 0;       /* [HGM] shuffle */
1982    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983    switch(newVariant) 
1984      {
1985      case VariantShogi:
1986        newWidth = 9;  newHeight = 9;
1987        gameInfo.holdingsSize = 7;
1988      case VariantBughouse:
1989      case VariantCrazyhouse:
1990        newHoldingsWidth = 2; break;
1991      case VariantGreat:
1992        newWidth = 10;
1993      case VariantSuper:
1994        newHoldingsWidth = 2;
1995        gameInfo.holdingsSize = 8;
1996        break;
1997      case VariantGothic:
1998      case VariantCapablanca:
1999      case VariantCapaRandom:
2000        newWidth = 10;
2001      default:
2002        newHoldingsWidth = gameInfo.holdingsSize = 0;
2003      };
2004    
2005    if(newWidth  != gameInfo.boardWidth  ||
2006       newHeight != gameInfo.boardHeight ||
2007       newHoldingsWidth != gameInfo.holdingsWidth ) {
2008      
2009      /* shift position to new playing area, if needed */
2010      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2011        for(i=0; i<BOARD_HEIGHT; i++) 
2012          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2013            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014              board[i][j];
2015        for(i=0; i<newHeight; i++) {
2016          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2017          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018        }
2019      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2020        for(i=0; i<BOARD_HEIGHT; i++)
2021          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2022            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2023              board[i][j];
2024      }
2025      gameInfo.boardWidth  = newWidth;
2026      gameInfo.boardHeight = newHeight;
2027      gameInfo.holdingsWidth = newHoldingsWidth;
2028      gameInfo.variant = newVariant;
2029      InitDrawingSizes(-2, 0);
2030    } else gameInfo.variant = newVariant;
2031    CopyBoard(oldBoard, board);   // remember correctly formatted board
2032      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2033    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2034 }
2035
2036 static int loggedOn = FALSE;
2037
2038 /*-- Game start info cache: --*/
2039 int gs_gamenum;
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static char cont_seq[] = "\n\\   ";
2044 static int player1Rating = -1;
2045 static int player2Rating = -1;
2046 /*----------------------------*/
2047
2048 ColorClass curColor = ColorNormal;
2049 int suppressKibitz = 0;
2050
2051 void
2052 read_from_ics(isr, closure, data, count, error)
2053      InputSourceRef isr;
2054      VOIDSTAR closure;
2055      char *data;
2056      int count;
2057      int error;
2058 {
2059 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2060 #define STARTED_NONE 0
2061 #define STARTED_MOVES 1
2062 #define STARTED_BOARD 2
2063 #define STARTED_OBSERVE 3
2064 #define STARTED_HOLDINGS 4
2065 #define STARTED_CHATTER 5
2066 #define STARTED_COMMENT 6
2067 #define STARTED_MOVES_NOHIDE 7
2068     
2069     static int started = STARTED_NONE;
2070     static char parse[20000];
2071     static int parse_pos = 0;
2072     static char buf[BUF_SIZE + 1];
2073     static int firstTime = TRUE, intfSet = FALSE;
2074     static ColorClass prevColor = ColorNormal;
2075     static int savingComment = FALSE;
2076     static int cmatch = 0; // continuation sequence match
2077     char *bp;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085     char talker[MSG_SIZ]; // [HGM] chat
2086     int channel;
2087
2088     if (appData.debugMode) {
2089       if (!error) {
2090         fprintf(debugFP, "<ICS: ");
2091         show_bytes(debugFP, data, count);
2092         fprintf(debugFP, "\n");
2093       }
2094     }
2095
2096     if (appData.debugMode) { int f = forwardMostMove;
2097         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2098                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2099                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2100     }
2101     if (count > 0) {
2102         /* If last read ended with a partial line that we couldn't parse,
2103            prepend it to the new read and try again. */
2104         if (leftover_len > 0) {
2105             for (i=0; i<leftover_len; i++)
2106               buf[i] = buf[leftover_start + i];
2107         }
2108
2109     /* copy new characters into the buffer */
2110     bp = buf + leftover_len;
2111     buf_len=leftover_len;
2112     for (i=0; i<count; i++)
2113     {
2114         // ignore these
2115         if (data[i] == '\r')
2116             continue;
2117
2118         // join lines split by ICS?
2119         if (!appData.noJoin)
2120         {
2121             /*
2122                 Joining just consists of finding matches against the
2123                 continuation sequence, and discarding that sequence
2124                 if found instead of copying it.  So, until a match
2125                 fails, there's nothing to do since it might be the
2126                 complete sequence, and thus, something we don't want
2127                 copied.
2128             */
2129             if (data[i] == cont_seq[cmatch])
2130             {
2131                 cmatch++;
2132                 if (cmatch == strlen(cont_seq))
2133                 {
2134                     cmatch = 0; // complete match.  just reset the counter
2135
2136                     /*
2137                         it's possible for the ICS to not include the space
2138                         at the end of the last word, making our [correct]
2139                         join operation fuse two separate words.  the server
2140                         does this when the space occurs at the width setting.
2141                     */
2142                     if (!buf_len || buf[buf_len-1] != ' ')
2143                     {
2144                         *bp++ = ' ';
2145                         buf_len++;
2146                     }
2147                 }
2148                 continue;
2149             }
2150             else if (cmatch)
2151             {
2152                 /*
2153                     match failed, so we have to copy what matched before
2154                     falling through and copying this character.  In reality,
2155                     this will only ever be just the newline character, but
2156                     it doesn't hurt to be precise.
2157                 */
2158                 strncpy(bp, cont_seq, cmatch);
2159                 bp += cmatch;
2160                 buf_len += cmatch;
2161                 cmatch = 0;
2162             }
2163         }
2164
2165         // copy this char
2166         *bp++ = data[i];
2167         buf_len++;
2168     }
2169
2170         buf[buf_len] = NULLCHAR;
2171         next_out = leftover_len;
2172         leftover_start = 0;
2173         
2174         i = 0;
2175         while (i < buf_len) {
2176             /* Deal with part of the TELNET option negotiation
2177                protocol.  We refuse to do anything beyond the
2178                defaults, except that we allow the WILL ECHO option,
2179                which ICS uses to turn off password echoing when we are
2180                directly connected to it.  We reject this option
2181                if localLineEditing mode is on (always on in xboard)
2182                and we are talking to port 23, which might be a real
2183                telnet server that will try to keep WILL ECHO on permanently.
2184              */
2185             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2186                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2187                 unsigned char option;
2188                 oldi = i;
2189                 switch ((unsigned char) buf[++i]) {
2190                   case TN_WILL:
2191                     if (appData.debugMode)
2192                       fprintf(debugFP, "\n<WILL ");
2193                     switch (option = (unsigned char) buf[++i]) {
2194                       case TN_ECHO:
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "ECHO ");
2197                         /* Reply only if this is a change, according
2198                            to the protocol rules. */
2199                         if (remoteEchoOption) break;
2200                         if (appData.localLineEditing &&
2201                             atoi(appData.icsPort) == TN_PORT) {
2202                             TelnetRequest(TN_DONT, TN_ECHO);
2203                         } else {
2204                             EchoOff();
2205                             TelnetRequest(TN_DO, TN_ECHO);
2206                             remoteEchoOption = TRUE;
2207                         }
2208                         break;
2209                       default:
2210                         if (appData.debugMode)
2211                           fprintf(debugFP, "%d ", option);
2212                         /* Whatever this is, we don't want it. */
2213                         TelnetRequest(TN_DONT, option);
2214                         break;
2215                     }
2216                     break;
2217                   case TN_WONT:
2218                     if (appData.debugMode)
2219                       fprintf(debugFP, "\n<WONT ");
2220                     switch (option = (unsigned char) buf[++i]) {
2221                       case TN_ECHO:
2222                         if (appData.debugMode)
2223                           fprintf(debugFP, "ECHO ");
2224                         /* Reply only if this is a change, according
2225                            to the protocol rules. */
2226                         if (!remoteEchoOption) break;
2227                         EchoOn();
2228                         TelnetRequest(TN_DONT, TN_ECHO);
2229                         remoteEchoOption = FALSE;
2230                         break;
2231                       default:
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "%d ", (unsigned char) option);
2234                         /* Whatever this is, it must already be turned
2235                            off, because we never agree to turn on
2236                            anything non-default, so according to the
2237                            protocol rules, we don't reply. */
2238                         break;
2239                     }
2240                     break;
2241                   case TN_DO:
2242                     if (appData.debugMode)
2243                       fprintf(debugFP, "\n<DO ");
2244                     switch (option = (unsigned char) buf[++i]) {
2245                       default:
2246                         /* Whatever this is, we refuse to do it. */
2247                         if (appData.debugMode)
2248                           fprintf(debugFP, "%d ", option);
2249                         TelnetRequest(TN_WONT, option);
2250                         break;
2251                     }
2252                     break;
2253                   case TN_DONT:
2254                     if (appData.debugMode)
2255                       fprintf(debugFP, "\n<DONT ");
2256                     switch (option = (unsigned char) buf[++i]) {
2257                       default:
2258                         if (appData.debugMode)
2259                           fprintf(debugFP, "%d ", option);
2260                         /* Whatever this is, we are already not doing
2261                            it, because we never agree to do anything
2262                            non-default, so according to the protocol
2263                            rules, we don't reply. */
2264                         break;
2265                     }
2266                     break;
2267                   case TN_IAC:
2268                     if (appData.debugMode)
2269                       fprintf(debugFP, "\n<IAC ");
2270                     /* Doubled IAC; pass it through */
2271                     i--;
2272                     break;
2273                   default:
2274                     if (appData.debugMode)
2275                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2276                     /* Drop all other telnet commands on the floor */
2277                     break;
2278                 }
2279                 if (oldi > next_out)
2280                   SendToPlayer(&buf[next_out], oldi - next_out);
2281                 if (++i > next_out)
2282                   next_out = i;
2283                 continue;
2284             }
2285                 
2286             /* OK, this at least will *usually* work */
2287             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2288                 loggedOn = TRUE;
2289             }
2290             
2291             if (loggedOn && !intfSet) {
2292                 if (ics_type == ICS_ICC) {
2293                   sprintf(str,
2294                           "/set-quietly interface %s\n/set-quietly style 12\n",
2295                           programVersion);
2296                 } else if (ics_type == ICS_CHESSNET) {
2297                   sprintf(str, "/style 12\n");
2298                 } else {
2299                   strcpy(str, "alias $ @\n$set interface ");
2300                   strcat(str, programVersion);
2301                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2302 #ifdef WIN32
2303                   strcat(str, "$iset nohighlight 1\n");
2304 #endif
2305                   strcat(str, "$iset lock 1\n$style 12\n");
2306                 }
2307                 SendToICS(str);
2308                 NotifyFrontendLogin();
2309                 intfSet = TRUE;
2310             }
2311
2312             if (started == STARTED_COMMENT) {
2313                 /* Accumulate characters in comment */
2314                 parse[parse_pos++] = buf[i];
2315                 if (buf[i] == '\n') {
2316                     parse[parse_pos] = NULLCHAR;
2317                     if(chattingPartner>=0) {
2318                         char mess[MSG_SIZ];
2319                         sprintf(mess, "%s%s", talker, parse);
2320                         OutputChatMessage(chattingPartner, mess);
2321                         chattingPartner = -1;
2322                     } else
2323                     if(!suppressKibitz) // [HGM] kibitz
2324                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2325                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2326                         int nrDigit = 0, nrAlph = 0, i;
2327                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2328                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2329                         parse[parse_pos] = NULLCHAR;
2330                         // try to be smart: if it does not look like search info, it should go to
2331                         // ICS interaction window after all, not to engine-output window.
2332                         for(i=0; i<parse_pos; i++) { // count letters and digits
2333                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2334                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2335                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2336                         }
2337                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2338                             int depth=0; float score;
2339                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2340                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2341                                 pvInfoList[forwardMostMove-1].depth = depth;
2342                                 pvInfoList[forwardMostMove-1].score = 100*score;
2343                             }
2344                             OutputKibitz(suppressKibitz, parse);
2345                         } else {
2346                             char tmp[MSG_SIZ];
2347                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2348                             SendToPlayer(tmp, strlen(tmp));
2349                         }
2350                     }
2351                     started = STARTED_NONE;
2352                 } else {
2353                     /* Don't match patterns against characters in chatter */
2354                     i++;
2355                     continue;
2356                 }
2357             }
2358             if (started == STARTED_CHATTER) {
2359                 if (buf[i] != '\n') {
2360                     /* Don't match patterns against characters in chatter */
2361                     i++;
2362                     continue;
2363                 }
2364                 started = STARTED_NONE;
2365             }
2366
2367             /* Kludge to deal with rcmd protocol */
2368             if (firstTime && looking_at(buf, &i, "\001*")) {
2369                 DisplayFatalError(&buf[1], 0, 1);
2370                 continue;
2371             } else {
2372                 firstTime = FALSE;
2373             }
2374
2375             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2376                 ics_type = ICS_ICC;
2377                 ics_prefix = "/";
2378                 if (appData.debugMode)
2379                   fprintf(debugFP, "ics_type %d\n", ics_type);
2380                 continue;
2381             }
2382             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2383                 ics_type = ICS_FICS;
2384                 ics_prefix = "$";
2385                 if (appData.debugMode)
2386                   fprintf(debugFP, "ics_type %d\n", ics_type);
2387                 continue;
2388             }
2389             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2390                 ics_type = ICS_CHESSNET;
2391                 ics_prefix = "/";
2392                 if (appData.debugMode)
2393                   fprintf(debugFP, "ics_type %d\n", ics_type);
2394                 continue;
2395             }
2396
2397             if (!loggedOn &&
2398                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2399                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2400                  looking_at(buf, &i, "will be \"*\""))) {
2401               strcpy(ics_handle, star_match[0]);
2402               continue;
2403             }
2404
2405             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2406               char buf[MSG_SIZ];
2407               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2408               DisplayIcsInteractionTitle(buf);
2409               have_set_title = TRUE;
2410             }
2411
2412             /* skip finger notes */
2413             if (started == STARTED_NONE &&
2414                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2415                  (buf[i] == '1' && buf[i+1] == '0')) &&
2416                 buf[i+2] == ':' && buf[i+3] == ' ') {
2417               started = STARTED_CHATTER;
2418               i += 3;
2419               continue;
2420             }
2421
2422             /* skip formula vars */
2423             if (started == STARTED_NONE &&
2424                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2425               started = STARTED_CHATTER;
2426               i += 3;
2427               continue;
2428             }
2429
2430             oldi = i;
2431             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2432             if (appData.autoKibitz && started == STARTED_NONE && 
2433                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2434                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2435                 if(looking_at(buf, &i, "* kibitzes: ") &&
2436                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2437                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2438                         suppressKibitz = TRUE;
2439                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2440                                 && (gameMode == IcsPlayingWhite)) ||
2441                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2442                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2443                             started = STARTED_CHATTER; // own kibitz we simply discard
2444                         else {
2445                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2446                             parse_pos = 0; parse[0] = NULLCHAR;
2447                             savingComment = TRUE;
2448                             suppressKibitz = gameMode != IcsObserving ? 2 :
2449                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2450                         } 
2451                         continue;
2452                 } else
2453                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2454                     started = STARTED_CHATTER;
2455                     suppressKibitz = TRUE;
2456                 }
2457             } // [HGM] kibitz: end of patch
2458
2459 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2460
2461             // [HGM] chat: intercept tells by users for which we have an open chat window
2462             channel = -1;
2463             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2464                                            looking_at(buf, &i, "* whispers:") ||
2465                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2466                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2467                 int p;
2468                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2469                 chattingPartner = -1;
2470
2471                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2472                 for(p=0; p<MAX_CHAT; p++) {
2473                     if(channel == atoi(chatPartner[p])) {
2474                     talker[0] = '['; strcat(talker, "]");
2475                     chattingPartner = p; break;
2476                     }
2477                 } else
2478                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2479                 for(p=0; p<MAX_CHAT; p++) {
2480                     if(!strcmp("WHISPER", chatPartner[p])) {
2481                         talker[0] = '['; strcat(talker, "]");
2482                         chattingPartner = p; break;
2483                     }
2484                 }
2485                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2486                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2487                     talker[0] = 0;
2488                     chattingPartner = p; break;
2489                 }
2490                 if(chattingPartner<0) i = oldi; else {
2491                     started = STARTED_COMMENT;
2492                     parse_pos = 0; parse[0] = NULLCHAR;
2493                     savingComment = TRUE;
2494                     suppressKibitz = TRUE;
2495                 }
2496             } // [HGM] chat: end of patch
2497
2498             if (appData.zippyTalk || appData.zippyPlay) {
2499                 /* [DM] Backup address for color zippy lines */
2500                 backup = i;
2501 #if ZIPPY
2502        #ifdef WIN32
2503                if (loggedOn == TRUE)
2504                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2505                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2506        #else
2507                 if (ZippyControl(buf, &i) ||
2508                     ZippyConverse(buf, &i) ||
2509                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2510                       loggedOn = TRUE;
2511                       if (!appData.colorize) continue;
2512                 }
2513        #endif
2514 #endif
2515             } // [DM] 'else { ' deleted
2516                 if (
2517                     /* Regular tells and says */
2518                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2519                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2520                     looking_at(buf, &i, "* says: ") ||
2521                     /* Don't color "message" or "messages" output */
2522                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2523                     looking_at(buf, &i, "*. * at *:*: ") ||
2524                     looking_at(buf, &i, "--* (*:*): ") ||
2525                     /* Message notifications (same color as tells) */
2526                     looking_at(buf, &i, "* has left a message ") ||
2527                     looking_at(buf, &i, "* just sent you a message:\n") ||
2528                     /* Whispers and kibitzes */
2529                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2530                     looking_at(buf, &i, "* kibitzes: ") ||
2531                     /* Channel tells */
2532                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2533
2534                   if (tkind == 1 && strchr(star_match[0], ':')) {
2535                       /* Avoid "tells you:" spoofs in channels */
2536                      tkind = 3;
2537                   }
2538                   if (star_match[0][0] == NULLCHAR ||
2539                       strchr(star_match[0], ' ') ||
2540                       (tkind == 3 && strchr(star_match[1], ' '))) {
2541                     /* Reject bogus matches */
2542                     i = oldi;
2543                   } else {
2544                     if (appData.colorize) {
2545                       if (oldi > next_out) {
2546                         SendToPlayer(&buf[next_out], oldi - next_out);
2547                         next_out = oldi;
2548                       }
2549                       switch (tkind) {
2550                       case 1:
2551                         Colorize(ColorTell, FALSE);
2552                         curColor = ColorTell;
2553                         break;
2554                       case 2:
2555                         Colorize(ColorKibitz, FALSE);
2556                         curColor = ColorKibitz;
2557                         break;
2558                       case 3:
2559                         p = strrchr(star_match[1], '(');
2560                         if (p == NULL) {
2561                           p = star_match[1];
2562                         } else {
2563                           p++;
2564                         }
2565                         if (atoi(p) == 1) {
2566                           Colorize(ColorChannel1, FALSE);
2567                           curColor = ColorChannel1;
2568                         } else {
2569                           Colorize(ColorChannel, FALSE);
2570                           curColor = ColorChannel;
2571                         }
2572                         break;
2573                       case 5:
2574                         curColor = ColorNormal;
2575                         break;
2576                       }
2577                     }
2578                     if (started == STARTED_NONE && appData.autoComment &&
2579                         (gameMode == IcsObserving ||
2580                          gameMode == IcsPlayingWhite ||
2581                          gameMode == IcsPlayingBlack)) {
2582                       parse_pos = i - oldi;
2583                       memcpy(parse, &buf[oldi], parse_pos);
2584                       parse[parse_pos] = NULLCHAR;
2585                       started = STARTED_COMMENT;
2586                       savingComment = TRUE;
2587                     } else {
2588                       started = STARTED_CHATTER;
2589                       savingComment = FALSE;
2590                     }
2591                     loggedOn = TRUE;
2592                     continue;
2593                   }
2594                 }
2595
2596                 if (looking_at(buf, &i, "* s-shouts: ") ||
2597                     looking_at(buf, &i, "* c-shouts: ")) {
2598                     if (appData.colorize) {
2599                         if (oldi > next_out) {
2600                             SendToPlayer(&buf[next_out], oldi - next_out);
2601                             next_out = oldi;
2602                         }
2603                         Colorize(ColorSShout, FALSE);
2604                         curColor = ColorSShout;
2605                     }
2606                     loggedOn = TRUE;
2607                     started = STARTED_CHATTER;
2608                     continue;
2609                 }
2610
2611                 if (looking_at(buf, &i, "--->")) {
2612                     loggedOn = TRUE;
2613                     continue;
2614                 }
2615
2616                 if (looking_at(buf, &i, "* shouts: ") ||
2617                     looking_at(buf, &i, "--> ")) {
2618                     if (appData.colorize) {
2619                         if (oldi > next_out) {
2620                             SendToPlayer(&buf[next_out], oldi - next_out);
2621                             next_out = oldi;
2622                         }
2623                         Colorize(ColorShout, FALSE);
2624                         curColor = ColorShout;
2625                     }
2626                     loggedOn = TRUE;
2627                     started = STARTED_CHATTER;
2628                     continue;
2629                 }
2630
2631                 if (looking_at( buf, &i, "Challenge:")) {
2632                     if (appData.colorize) {
2633                         if (oldi > next_out) {
2634                             SendToPlayer(&buf[next_out], oldi - next_out);
2635                             next_out = oldi;
2636                         }
2637                         Colorize(ColorChallenge, FALSE);
2638                         curColor = ColorChallenge;
2639                     }
2640                     loggedOn = TRUE;
2641                     continue;
2642                 }
2643
2644                 if (looking_at(buf, &i, "* offers you") ||
2645                     looking_at(buf, &i, "* offers to be") ||
2646                     looking_at(buf, &i, "* would like to") ||
2647                     looking_at(buf, &i, "* requests to") ||
2648                     looking_at(buf, &i, "Your opponent offers") ||
2649                     looking_at(buf, &i, "Your opponent requests")) {
2650
2651                     if (appData.colorize) {
2652                         if (oldi > next_out) {
2653                             SendToPlayer(&buf[next_out], oldi - next_out);
2654                             next_out = oldi;
2655                         }
2656                         Colorize(ColorRequest, FALSE);
2657                         curColor = ColorRequest;
2658                     }
2659                     continue;
2660                 }
2661
2662                 if (looking_at(buf, &i, "* (*) seeking")) {
2663                     if (appData.colorize) {
2664                         if (oldi > next_out) {
2665                             SendToPlayer(&buf[next_out], oldi - next_out);
2666                             next_out = oldi;
2667                         }
2668                         Colorize(ColorSeek, FALSE);
2669                         curColor = ColorSeek;
2670                     }
2671                     continue;
2672             }
2673
2674             if (looking_at(buf, &i, "\\   ")) {
2675                 if (prevColor != ColorNormal) {
2676                     if (oldi > next_out) {
2677                         SendToPlayer(&buf[next_out], oldi - next_out);
2678                         next_out = oldi;
2679                     }
2680                     Colorize(prevColor, TRUE);
2681                     curColor = prevColor;
2682                 }
2683                 if (savingComment) {
2684                     parse_pos = i - oldi;
2685                     memcpy(parse, &buf[oldi], parse_pos);
2686                     parse[parse_pos] = NULLCHAR;
2687                     started = STARTED_COMMENT;
2688                 } else {
2689                     started = STARTED_CHATTER;
2690                 }
2691                 continue;
2692             }
2693
2694             if (looking_at(buf, &i, "Black Strength :") ||
2695                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2696                 looking_at(buf, &i, "<10>") ||
2697                 looking_at(buf, &i, "#@#")) {
2698                 /* Wrong board style */
2699                 loggedOn = TRUE;
2700                 SendToICS(ics_prefix);
2701                 SendToICS("set style 12\n");
2702                 SendToICS(ics_prefix);
2703                 SendToICS("refresh\n");
2704                 continue;
2705             }
2706             
2707             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2708                 ICSInitScript();
2709                 have_sent_ICS_logon = 1;
2710                 continue;
2711             }
2712               
2713             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2714                 (looking_at(buf, &i, "\n<12> ") ||
2715                  looking_at(buf, &i, "<12> "))) {
2716                 loggedOn = TRUE;
2717                 if (oldi > next_out) {
2718                     SendToPlayer(&buf[next_out], oldi - next_out);
2719                 }
2720                 next_out = i;
2721                 started = STARTED_BOARD;
2722                 parse_pos = 0;
2723                 continue;
2724             }
2725
2726             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2727                 looking_at(buf, &i, "<b1> ")) {
2728                 if (oldi > next_out) {
2729                     SendToPlayer(&buf[next_out], oldi - next_out);
2730                 }
2731                 next_out = i;
2732                 started = STARTED_HOLDINGS;
2733                 parse_pos = 0;
2734                 continue;
2735             }
2736
2737             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2738                 loggedOn = TRUE;
2739                 /* Header for a move list -- first line */
2740
2741                 switch (ics_getting_history) {
2742                   case H_FALSE:
2743                     switch (gameMode) {
2744                       case IcsIdle:
2745                       case BeginningOfGame:
2746                         /* User typed "moves" or "oldmoves" while we
2747                            were idle.  Pretend we asked for these
2748                            moves and soak them up so user can step
2749                            through them and/or save them.
2750                            */
2751                         Reset(FALSE, TRUE);
2752                         gameMode = IcsObserving;
2753                         ModeHighlight();
2754                         ics_gamenum = -1;
2755                         ics_getting_history = H_GOT_UNREQ_HEADER;
2756                         break;
2757                       case EditGame: /*?*/
2758                       case EditPosition: /*?*/
2759                         /* Should above feature work in these modes too? */
2760                         /* For now it doesn't */
2761                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2762                         break;
2763                       default:
2764                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2765                         break;
2766                     }
2767                     break;
2768                   case H_REQUESTED:
2769                     /* Is this the right one? */
2770                     if (gameInfo.white && gameInfo.black &&
2771                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2772                         strcmp(gameInfo.black, star_match[2]) == 0) {
2773                         /* All is well */
2774                         ics_getting_history = H_GOT_REQ_HEADER;
2775                     }
2776                     break;
2777                   case H_GOT_REQ_HEADER:
2778                   case H_GOT_UNREQ_HEADER:
2779                   case H_GOT_UNWANTED_HEADER:
2780                   case H_GETTING_MOVES:
2781                     /* Should not happen */
2782                     DisplayError(_("Error gathering move list: two headers"), 0);
2783                     ics_getting_history = H_FALSE;
2784                     break;
2785                 }
2786
2787                 /* Save player ratings into gameInfo if needed */
2788                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2789                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2790                     (gameInfo.whiteRating == -1 ||
2791                      gameInfo.blackRating == -1)) {
2792
2793                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2794                     gameInfo.blackRating = string_to_rating(star_match[3]);
2795                     if (appData.debugMode)
2796                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2797                               gameInfo.whiteRating, gameInfo.blackRating);
2798                 }
2799                 continue;
2800             }
2801
2802             if (looking_at(buf, &i,
2803               "* * match, initial time: * minute*, increment: * second")) {
2804                 /* Header for a move list -- second line */
2805                 /* Initial board will follow if this is a wild game */
2806                 if (gameInfo.event != NULL) free(gameInfo.event);
2807                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2808                 gameInfo.event = StrSave(str);
2809                 /* [HGM] we switched variant. Translate boards if needed. */
2810                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2811                 continue;
2812             }
2813
2814             if (looking_at(buf, &i, "Move  ")) {
2815                 /* Beginning of a move list */
2816                 switch (ics_getting_history) {
2817                   case H_FALSE:
2818                     /* Normally should not happen */
2819                     /* Maybe user hit reset while we were parsing */
2820                     break;
2821                   case H_REQUESTED:
2822                     /* Happens if we are ignoring a move list that is not
2823                      * the one we just requested.  Common if the user
2824                      * tries to observe two games without turning off
2825                      * getMoveList */
2826                     break;
2827                   case H_GETTING_MOVES:
2828                     /* Should not happen */
2829                     DisplayError(_("Error gathering move list: nested"), 0);
2830                     ics_getting_history = H_FALSE;
2831                     break;
2832                   case H_GOT_REQ_HEADER:
2833                     ics_getting_history = H_GETTING_MOVES;
2834                     started = STARTED_MOVES;
2835                     parse_pos = 0;
2836                     if (oldi > next_out) {
2837                         SendToPlayer(&buf[next_out], oldi - next_out);
2838                     }
2839                     break;
2840                   case H_GOT_UNREQ_HEADER:
2841                     ics_getting_history = H_GETTING_MOVES;
2842                     started = STARTED_MOVES_NOHIDE;
2843                     parse_pos = 0;
2844                     break;
2845                   case H_GOT_UNWANTED_HEADER:
2846                     ics_getting_history = H_FALSE;
2847                     break;
2848                 }
2849                 continue;
2850             }                           
2851             
2852             if (looking_at(buf, &i, "% ") ||
2853                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2854                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2855                 savingComment = FALSE;
2856                 switch (started) {
2857                   case STARTED_MOVES:
2858                   case STARTED_MOVES_NOHIDE:
2859                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2860                     parse[parse_pos + i - oldi] = NULLCHAR;
2861                     ParseGameHistory(parse);
2862 #if ZIPPY
2863                     if (appData.zippyPlay && first.initDone) {
2864                         FeedMovesToProgram(&first, forwardMostMove);
2865                         if (gameMode == IcsPlayingWhite) {
2866                             if (WhiteOnMove(forwardMostMove)) {
2867                                 if (first.sendTime) {
2868                                   if (first.useColors) {
2869                                     SendToProgram("black\n", &first); 
2870                                   }
2871                                   SendTimeRemaining(&first, TRUE);
2872                                 }
2873                                 if (first.useColors) {
2874                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2875                                 }
2876                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2877                                 first.maybeThinking = TRUE;
2878                             } else {
2879                                 if (first.usePlayother) {
2880                                   if (first.sendTime) {
2881                                     SendTimeRemaining(&first, TRUE);
2882                                   }
2883                                   SendToProgram("playother\n", &first);
2884                                   firstMove = FALSE;
2885                                 } else {
2886                                   firstMove = TRUE;
2887                                 }
2888                             }
2889                         } else if (gameMode == IcsPlayingBlack) {
2890                             if (!WhiteOnMove(forwardMostMove)) {
2891                                 if (first.sendTime) {
2892                                   if (first.useColors) {
2893                                     SendToProgram("white\n", &first);
2894                                   }
2895                                   SendTimeRemaining(&first, FALSE);
2896                                 }
2897                                 if (first.useColors) {
2898                                   SendToProgram("black\n", &first);
2899                                 }
2900                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2901                                 first.maybeThinking = TRUE;
2902                             } else {
2903                                 if (first.usePlayother) {
2904                                   if (first.sendTime) {
2905                                     SendTimeRemaining(&first, FALSE);
2906                                   }
2907                                   SendToProgram("playother\n", &first);
2908                                   firstMove = FALSE;
2909                                 } else {
2910                                   firstMove = TRUE;
2911                                 }
2912                             }
2913                         }                       
2914                     }
2915 #endif
2916                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2917                         /* Moves came from oldmoves or moves command
2918                            while we weren't doing anything else.
2919                            */
2920                         currentMove = forwardMostMove;
2921                         ClearHighlights();/*!!could figure this out*/
2922                         flipView = appData.flipView;
2923                         DrawPosition(TRUE, boards[currentMove]);
2924                         DisplayBothClocks();
2925                         sprintf(str, "%s vs. %s",
2926                                 gameInfo.white, gameInfo.black);
2927                         DisplayTitle(str);
2928                         gameMode = IcsIdle;
2929                     } else {
2930                         /* Moves were history of an active game */
2931                         if (gameInfo.resultDetails != NULL) {
2932                             free(gameInfo.resultDetails);
2933                             gameInfo.resultDetails = NULL;
2934                         }
2935                     }
2936                     HistorySet(parseList, backwardMostMove,
2937                                forwardMostMove, currentMove-1);
2938                     DisplayMove(currentMove - 1);
2939                     if (started == STARTED_MOVES) next_out = i;
2940                     started = STARTED_NONE;
2941                     ics_getting_history = H_FALSE;
2942                     break;
2943
2944                   case STARTED_OBSERVE:
2945                     started = STARTED_NONE;
2946                     SendToICS(ics_prefix);
2947                     SendToICS("refresh\n");
2948                     break;
2949
2950                   default:
2951                     break;
2952                 }
2953                 if(bookHit) { // [HGM] book: simulate book reply
2954                     static char bookMove[MSG_SIZ]; // a bit generous?
2955
2956                     programStats.nodes = programStats.depth = programStats.time = 
2957                     programStats.score = programStats.got_only_move = 0;
2958                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2959
2960                     strcpy(bookMove, "move ");
2961                     strcat(bookMove, bookHit);
2962                     HandleMachineMove(bookMove, &first);
2963                 }
2964                 continue;
2965             }
2966             
2967             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2968                  started == STARTED_HOLDINGS ||
2969                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2970                 /* Accumulate characters in move list or board */
2971                 parse[parse_pos++] = buf[i];
2972             }
2973             
2974             /* Start of game messages.  Mostly we detect start of game
2975                when the first board image arrives.  On some versions
2976                of the ICS, though, we need to do a "refresh" after starting
2977                to observe in order to get the current board right away. */
2978             if (looking_at(buf, &i, "Adding game * to observation list")) {
2979                 started = STARTED_OBSERVE;
2980                 continue;
2981             }
2982
2983             /* Handle auto-observe */
2984             if (appData.autoObserve &&
2985                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2986                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2987                 char *player;
2988                 /* Choose the player that was highlighted, if any. */
2989                 if (star_match[0][0] == '\033' ||
2990                     star_match[1][0] != '\033') {
2991                     player = star_match[0];
2992                 } else {
2993                     player = star_match[2];
2994                 }
2995                 sprintf(str, "%sobserve %s\n",
2996                         ics_prefix, StripHighlightAndTitle(player));
2997                 SendToICS(str);
2998
2999                 /* Save ratings from notify string */
3000                 strcpy(player1Name, star_match[0]);
3001                 player1Rating = string_to_rating(star_match[1]);
3002                 strcpy(player2Name, star_match[2]);
3003                 player2Rating = string_to_rating(star_match[3]);
3004
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, 
3007                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3008                           player1Name, player1Rating,
3009                           player2Name, player2Rating);
3010
3011                 continue;
3012             }
3013
3014             /* Deal with automatic examine mode after a game,
3015                and with IcsObserving -> IcsExamining transition */
3016             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3017                 looking_at(buf, &i, "has made you an examiner of game *")) {
3018
3019                 int gamenum = atoi(star_match[0]);
3020                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3021                     gamenum == ics_gamenum) {
3022                     /* We were already playing or observing this game;
3023                        no need to refetch history */
3024                     gameMode = IcsExamining;
3025                     if (pausing) {
3026                         pauseExamForwardMostMove = forwardMostMove;
3027                     } else if (currentMove < forwardMostMove) {
3028                         ForwardInner(forwardMostMove);
3029                     }
3030                 } else {
3031                     /* I don't think this case really can happen */
3032                     SendToICS(ics_prefix);
3033                     SendToICS("refresh\n");
3034                 }
3035                 continue;
3036             }    
3037             
3038             /* Error messages */
3039 //          if (ics_user_moved) {
3040             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3041                 if (looking_at(buf, &i, "Illegal move") ||
3042                     looking_at(buf, &i, "Not a legal move") ||
3043                     looking_at(buf, &i, "Your king is in check") ||
3044                     looking_at(buf, &i, "It isn't your turn") ||
3045                     looking_at(buf, &i, "It is not your move")) {
3046                     /* Illegal move */
3047                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3048                         currentMove = --forwardMostMove;
3049                         DisplayMove(currentMove - 1); /* before DMError */
3050                         DrawPosition(FALSE, boards[currentMove]);
3051                         SwitchClocks();
3052                         DisplayBothClocks();
3053                     }
3054                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3055                     ics_user_moved = 0;
3056                     continue;
3057                 }
3058             }
3059
3060             if (looking_at(buf, &i, "still have time") ||
3061                 looking_at(buf, &i, "not out of time") ||
3062                 looking_at(buf, &i, "either player is out of time") ||
3063                 looking_at(buf, &i, "has timeseal; checking")) {
3064                 /* We must have called his flag a little too soon */
3065                 whiteFlag = blackFlag = FALSE;
3066                 continue;
3067             }
3068
3069             if (looking_at(buf, &i, "added * seconds to") ||
3070                 looking_at(buf, &i, "seconds were added to")) {
3071                 /* Update the clocks */
3072                 SendToICS(ics_prefix);
3073                 SendToICS("refresh\n");
3074                 continue;
3075             }
3076
3077             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3078                 ics_clock_paused = TRUE;
3079                 StopClocks();
3080                 continue;
3081             }
3082
3083             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3084                 ics_clock_paused = FALSE;
3085                 StartClocks();
3086                 continue;
3087             }
3088
3089             /* Grab player ratings from the Creating: message.
3090                Note we have to check for the special case when
3091                the ICS inserts things like [white] or [black]. */
3092             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3093                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3094                 /* star_matches:
3095                    0    player 1 name (not necessarily white)
3096                    1    player 1 rating
3097                    2    empty, white, or black (IGNORED)
3098                    3    player 2 name (not necessarily black)
3099                    4    player 2 rating
3100                    
3101                    The names/ratings are sorted out when the game
3102                    actually starts (below).
3103                 */
3104                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3105                 player1Rating = string_to_rating(star_match[1]);
3106                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3107                 player2Rating = string_to_rating(star_match[4]);
3108
3109                 if (appData.debugMode)
3110                   fprintf(debugFP, 
3111                           "Ratings from 'Creating:' %s %d, %s %d\n",
3112                           player1Name, player1Rating,
3113                           player2Name, player2Rating);
3114
3115                 continue;
3116             }
3117             
3118             /* Improved generic start/end-of-game messages */
3119             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3120                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3121                 /* If tkind == 0: */
3122                 /* star_match[0] is the game number */
3123                 /*           [1] is the white player's name */
3124                 /*           [2] is the black player's name */
3125                 /* For end-of-game: */
3126                 /*           [3] is the reason for the game end */
3127                 /*           [4] is a PGN end game-token, preceded by " " */
3128                 /* For start-of-game: */
3129                 /*           [3] begins with "Creating" or "Continuing" */
3130                 /*           [4] is " *" or empty (don't care). */
3131                 int gamenum = atoi(star_match[0]);
3132                 char *whitename, *blackname, *why, *endtoken;
3133                 ChessMove endtype = (ChessMove) 0;
3134
3135                 if (tkind == 0) {
3136                   whitename = star_match[1];
3137                   blackname = star_match[2];
3138                   why = star_match[3];
3139                   endtoken = star_match[4];
3140                 } else {
3141                   whitename = star_match[1];
3142                   blackname = star_match[3];
3143                   why = star_match[5];
3144                   endtoken = star_match[6];
3145                 }
3146
3147                 /* Game start messages */
3148                 if (strncmp(why, "Creating ", 9) == 0 ||
3149                     strncmp(why, "Continuing ", 11) == 0) {
3150                     gs_gamenum = gamenum;
3151                     strcpy(gs_kind, strchr(why, ' ') + 1);
3152 #if ZIPPY
3153                     if (appData.zippyPlay) {
3154                         ZippyGameStart(whitename, blackname);
3155                     }
3156 #endif /*ZIPPY*/
3157                     continue;
3158                 }
3159
3160                 /* Game end messages */
3161                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3162                     ics_gamenum != gamenum) {
3163                     continue;
3164                 }
3165                 while (endtoken[0] == ' ') endtoken++;
3166                 switch (endtoken[0]) {
3167                   case '*':
3168                   default:
3169                     endtype = GameUnfinished;
3170                     break;
3171                   case '0':
3172                     endtype = BlackWins;
3173                     break;
3174                   case '1':
3175                     if (endtoken[1] == '/')
3176                       endtype = GameIsDrawn;
3177                     else
3178                       endtype = WhiteWins;
3179                     break;
3180                 }
3181                 GameEnds(endtype, why, GE_ICS);
3182 #if ZIPPY
3183                 if (appData.zippyPlay && first.initDone) {
3184                     ZippyGameEnd(endtype, why);
3185                     if (first.pr == NULL) {
3186                       /* Start the next process early so that we'll
3187                          be ready for the next challenge */
3188                       StartChessProgram(&first);
3189                     }
3190                     /* Send "new" early, in case this command takes
3191                        a long time to finish, so that we'll be ready
3192                        for the next challenge. */
3193                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3194                     Reset(TRUE, TRUE);
3195                 }
3196 #endif /*ZIPPY*/
3197                 continue;
3198             }
3199
3200             if (looking_at(buf, &i, "Removing game * from observation") ||
3201                 looking_at(buf, &i, "no longer observing game *") ||
3202                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3203                 if (gameMode == IcsObserving &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       /* icsEngineAnalyze */
3207                       if (appData.icsEngineAnalyze) {
3208                             ExitAnalyzeMode();
3209                             ModeHighlight();
3210                       }
3211                       StopClocks();
3212                       gameMode = IcsIdle;
3213                       ics_gamenum = -1;
3214                       ics_user_moved = FALSE;
3215                   }
3216                 continue;
3217             }
3218
3219             if (looking_at(buf, &i, "no longer examining game *")) {
3220                 if (gameMode == IcsExamining &&
3221                     atoi(star_match[0]) == ics_gamenum)
3222                   {
3223                       gameMode = IcsIdle;
3224                       ics_gamenum = -1;
3225                       ics_user_moved = FALSE;
3226                   }
3227                 continue;
3228             }
3229
3230             /* Advance leftover_start past any newlines we find,
3231                so only partial lines can get reparsed */
3232             if (looking_at(buf, &i, "\n")) {
3233                 prevColor = curColor;
3234                 if (curColor != ColorNormal) {
3235                     if (oldi > next_out) {
3236                         SendToPlayer(&buf[next_out], oldi - next_out);
3237                         next_out = oldi;
3238                     }
3239                     Colorize(ColorNormal, FALSE);
3240                     curColor = ColorNormal;
3241                 }
3242                 if (started == STARTED_BOARD) {
3243                     started = STARTED_NONE;
3244                     parse[parse_pos] = NULLCHAR;
3245                     ParseBoard12(parse);
3246                     ics_user_moved = 0;
3247
3248                     /* Send premove here */
3249                     if (appData.premove) {
3250                       char str[MSG_SIZ];
3251                       if (currentMove == 0 &&
3252                           gameMode == IcsPlayingWhite &&
3253                           appData.premoveWhite) {
3254                         sprintf(str, "%s\n", appData.premoveWhiteText);
3255                         if (appData.debugMode)
3256                           fprintf(debugFP, "Sending premove:\n");
3257                         SendToICS(str);
3258                       } else if (currentMove == 1 &&
3259                                  gameMode == IcsPlayingBlack &&
3260                                  appData.premoveBlack) {
3261                         sprintf(str, "%s\n", appData.premoveBlackText);
3262                         if (appData.debugMode)
3263                           fprintf(debugFP, "Sending premove:\n");
3264                         SendToICS(str);
3265                       } else if (gotPremove) {
3266                         gotPremove = 0;
3267                         ClearPremoveHighlights();
3268                         if (appData.debugMode)
3269                           fprintf(debugFP, "Sending premove:\n");
3270                           UserMoveEvent(premoveFromX, premoveFromY, 
3271                                         premoveToX, premoveToY, 
3272                                         premovePromoChar);
3273                       }
3274                     }
3275
3276                     /* Usually suppress following prompt */
3277                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3278                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3279                         if (looking_at(buf, &i, "*% ")) {
3280                             savingComment = FALSE;
3281                         }
3282                     }
3283                     next_out = i;
3284                 } else if (started == STARTED_HOLDINGS) {
3285                     int gamenum;
3286                     char new_piece[MSG_SIZ];
3287                     started = STARTED_NONE;
3288                     parse[parse_pos] = NULLCHAR;
3289                     if (appData.debugMode)
3290                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3291                                                         parse, currentMove);
3292                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3293                         gamenum == ics_gamenum) {
3294                         if (gameInfo.variant == VariantNormal) {
3295                           /* [HGM] We seem to switch variant during a game!
3296                            * Presumably no holdings were displayed, so we have
3297                            * to move the position two files to the right to
3298                            * create room for them!
3299                            */
3300                           VariantClass newVariant;
3301                           switch(gameInfo.boardWidth) { // base guess on board width
3302                                 case 9:  newVariant = VariantShogi; break;
3303                                 case 10: newVariant = VariantGreat; break;
3304                                 default: newVariant = VariantCrazyhouse; break;
3305                           }
3306                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3307                           /* Get a move list just to see the header, which
3308                              will tell us whether this is really bug or zh */
3309                           if (ics_getting_history == H_FALSE) {
3310                             ics_getting_history = H_REQUESTED;
3311                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3312                             SendToICS(str);
3313                           }
3314                         }
3315                         new_piece[0] = NULLCHAR;
3316                         sscanf(parse, "game %d white [%s black [%s <- %s",
3317                                &gamenum, white_holding, black_holding,
3318                                new_piece);
3319                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3320                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3321                         /* [HGM] copy holdings to board holdings area */
3322                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3323                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3324                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3325 #if ZIPPY
3326                         if (appData.zippyPlay && first.initDone) {
3327                             ZippyHoldings(white_holding, black_holding,
3328                                           new_piece);
3329                         }
3330 #endif /*ZIPPY*/
3331                         if (tinyLayout || smallLayout) {
3332                             char wh[16], bh[16];
3333                             PackHolding(wh, white_holding);
3334                             PackHolding(bh, black_holding);
3335                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3336                                     gameInfo.white, gameInfo.black);
3337                         } else {
3338                             sprintf(str, "%s [%s] vs. %s [%s]",
3339                                     gameInfo.white, white_holding,
3340                                     gameInfo.black, black_holding);
3341                         }
3342
3343                         DrawPosition(FALSE, boards[currentMove]);
3344                         DisplayTitle(str);
3345                     }
3346                     /* Suppress following prompt */
3347                     if (looking_at(buf, &i, "*% ")) {
3348                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3349                         savingComment = FALSE;
3350                     }
3351                     next_out = i;
3352                 }
3353                 continue;
3354             }
3355
3356             i++;                /* skip unparsed character and loop back */
3357         }
3358         
3359         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3360             started != STARTED_HOLDINGS && i > next_out) {
3361             SendToPlayer(&buf[next_out], i - next_out);
3362             next_out = i;
3363         }
3364         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3365         
3366         leftover_len = buf_len - leftover_start;
3367         /* if buffer ends with something we couldn't parse,
3368            reparse it after appending the next read */
3369         
3370     } else if (count == 0) {
3371         RemoveInputSource(isr);
3372         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3373     } else {
3374         DisplayFatalError(_("Error reading from ICS"), error, 1);
3375     }
3376 }
3377
3378
3379 /* Board style 12 looks like this:
3380    
3381    <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
3382    
3383  * The "<12> " is stripped before it gets to this routine.  The two
3384  * trailing 0's (flip state and clock ticking) are later addition, and
3385  * some chess servers may not have them, or may have only the first.
3386  * Additional trailing fields may be added in the future.  
3387  */
3388
3389 #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"
3390
3391 #define RELATION_OBSERVING_PLAYED    0
3392 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3393 #define RELATION_PLAYING_MYMOVE      1
3394 #define RELATION_PLAYING_NOTMYMOVE  -1
3395 #define RELATION_EXAMINING           2
3396 #define RELATION_ISOLATED_BOARD     -3
3397 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3398
3399 void
3400 ParseBoard12(string)
3401      char *string;
3402
3403     GameMode newGameMode;
3404     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3405     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3406     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3407     char to_play, board_chars[200];
3408     char move_str[500], str[500], elapsed_time[500];
3409     char black[32], white[32];
3410     Board board;
3411     int prevMove = currentMove;
3412     int ticking = 2;
3413     ChessMove moveType;
3414     int fromX, fromY, toX, toY;
3415     char promoChar;
3416     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3417     char *bookHit = NULL; // [HGM] book
3418     Boolean weird = FALSE, reqFlag = FALSE;
3419
3420     fromX = fromY = toX = toY = -1;
3421     
3422     newGame = FALSE;
3423
3424     if (appData.debugMode)
3425       fprintf(debugFP, _("Parsing board: %s\n"), string);
3426
3427     move_str[0] = NULLCHAR;
3428     elapsed_time[0] = NULLCHAR;
3429     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3430         int  i = 0, j;
3431         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3432             if(string[i] == ' ') { ranks++; files = 0; }
3433             else files++;
3434             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3435             i++;
3436         }
3437         for(j = 0; j <i; j++) board_chars[j] = string[j];
3438         board_chars[i] = '\0';
3439         string += i + 1;
3440     }
3441     n = sscanf(string, PATTERN, &to_play, &double_push,
3442                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3443                &gamenum, white, black, &relation, &basetime, &increment,
3444                &white_stren, &black_stren, &white_time, &black_time,
3445                &moveNum, str, elapsed_time, move_str, &ics_flip,
3446                &ticking);
3447
3448     if (n < 21) {
3449         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3450         DisplayError(str, 0);
3451         return;
3452     }
3453
3454     /* Convert the move number to internal form */
3455     moveNum = (moveNum - 1) * 2;
3456     if (to_play == 'B') moveNum++;
3457     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3458       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3459                         0, 1);
3460       return;
3461     }
3462     
3463     switch (relation) {
3464       case RELATION_OBSERVING_PLAYED:
3465       case RELATION_OBSERVING_STATIC:
3466         if (gamenum == -1) {
3467             /* Old ICC buglet */
3468             relation = RELATION_OBSERVING_STATIC;
3469         }
3470         newGameMode = IcsObserving;
3471         break;
3472       case RELATION_PLAYING_MYMOVE:
3473       case RELATION_PLAYING_NOTMYMOVE:
3474         newGameMode =
3475           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3476             IcsPlayingWhite : IcsPlayingBlack;
3477         break;
3478       case RELATION_EXAMINING:
3479         newGameMode = IcsExamining;
3480         break;
3481       case RELATION_ISOLATED_BOARD:
3482       default:
3483         /* Just display this board.  If user was doing something else,
3484            we will forget about it until the next board comes. */ 
3485         newGameMode = IcsIdle;
3486         break;
3487       case RELATION_STARTING_POSITION:
3488         newGameMode = gameMode;
3489         break;
3490     }
3491     
3492     /* Modify behavior for initial board display on move listing
3493        of wild games.
3494        */
3495     switch (ics_getting_history) {
3496       case H_FALSE:
3497       case H_REQUESTED:
3498         break;
3499       case H_GOT_REQ_HEADER:
3500       case H_GOT_UNREQ_HEADER:
3501         /* This is the initial position of the current game */
3502         gamenum = ics_gamenum;
3503         moveNum = 0;            /* old ICS bug workaround */
3504         if (to_play == 'B') {
3505           startedFromSetupPosition = TRUE;
3506           blackPlaysFirst = TRUE;
3507           moveNum = 1;
3508           if (forwardMostMove == 0) forwardMostMove = 1;
3509           if (backwardMostMove == 0) backwardMostMove = 1;
3510           if (currentMove == 0) currentMove = 1;
3511         }
3512         newGameMode = gameMode;
3513         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3514         break;
3515       case H_GOT_UNWANTED_HEADER:
3516         /* This is an initial board that we don't want */
3517         return;
3518       case H_GETTING_MOVES:
3519         /* Should not happen */
3520         DisplayError(_("Error gathering move list: extra board"), 0);
3521         ics_getting_history = H_FALSE;
3522         return;
3523     }
3524
3525    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3526                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3527      /* [HGM] We seem to have switched variant unexpectedly
3528       * Try to guess new variant from board size
3529       */
3530           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3531           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3532           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3533           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3534           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3535           if(!weird) newVariant = VariantNormal;
3536           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3537           /* Get a move list just to see the header, which
3538              will tell us whether this is really bug or zh */
3539           if (ics_getting_history == H_FALSE) {
3540             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3541             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3542             SendToICS(str);
3543           }
3544     }
3545     
3546     /* Take action if this is the first board of a new game, or of a
3547        different game than is currently being displayed.  */
3548     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3549         relation == RELATION_ISOLATED_BOARD) {
3550         
3551         /* Forget the old game and get the history (if any) of the new one */
3552         if (gameMode != BeginningOfGame) {
3553           Reset(TRUE, TRUE);
3554         }
3555         newGame = TRUE;
3556         if (appData.autoRaiseBoard) BoardToTop();
3557         prevMove = -3;
3558         if (gamenum == -1) {
3559             newGameMode = IcsIdle;
3560         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3561                    appData.getMoveList && !reqFlag) {
3562             /* Need to get game history */
3563             ics_getting_history = H_REQUESTED;
3564             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3565             SendToICS(str);
3566         }
3567         
3568         /* Initially flip the board to have black on the bottom if playing
3569            black or if the ICS flip flag is set, but let the user change
3570            it with the Flip View button. */
3571         flipView = appData.autoFlipView ? 
3572           (newGameMode == IcsPlayingBlack) || ics_flip :
3573           appData.flipView;
3574         
3575         /* Done with values from previous mode; copy in new ones */
3576         gameMode = newGameMode;
3577         ModeHighlight();
3578         ics_gamenum = gamenum;
3579         if (gamenum == gs_gamenum) {
3580             int klen = strlen(gs_kind);
3581             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3582             sprintf(str, "ICS %s", gs_kind);
3583             gameInfo.event = StrSave(str);
3584         } else {
3585             gameInfo.event = StrSave("ICS game");
3586         }
3587         gameInfo.site = StrSave(appData.icsHost);
3588         gameInfo.date = PGNDate();
3589         gameInfo.round = StrSave("-");
3590         gameInfo.white = StrSave(white);
3591         gameInfo.black = StrSave(black);
3592         timeControl = basetime * 60 * 1000;
3593         timeControl_2 = 0;
3594         timeIncrement = increment * 1000;
3595         movesPerSession = 0;
3596         gameInfo.timeControl = TimeControlTagValue();
3597         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3598   if (appData.debugMode) {
3599     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3600     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3601     setbuf(debugFP, NULL);
3602   }
3603
3604         gameInfo.outOfBook = NULL;
3605         
3606         /* Do we have the ratings? */
3607         if (strcmp(player1Name, white) == 0 &&
3608             strcmp(player2Name, black) == 0) {
3609             if (appData.debugMode)
3610               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3611                       player1Rating, player2Rating);
3612             gameInfo.whiteRating = player1Rating;
3613             gameInfo.blackRating = player2Rating;
3614         } else if (strcmp(player2Name, white) == 0 &&
3615                    strcmp(player1Name, black) == 0) {
3616             if (appData.debugMode)
3617               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3618                       player2Rating, player1Rating);
3619             gameInfo.whiteRating = player2Rating;
3620             gameInfo.blackRating = player1Rating;
3621         }
3622         player1Name[0] = player2Name[0] = NULLCHAR;
3623
3624         /* Silence shouts if requested */
3625         if (appData.quietPlay &&
3626             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3627             SendToICS(ics_prefix);
3628             SendToICS("set shout 0\n");
3629         }
3630     }
3631     
3632     /* Deal with midgame name changes */
3633     if (!newGame) {
3634         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3635             if (gameInfo.white) free(gameInfo.white);
3636             gameInfo.white = StrSave(white);
3637         }
3638         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3639             if (gameInfo.black) free(gameInfo.black);
3640             gameInfo.black = StrSave(black);
3641         }
3642     }
3643     
3644     /* Throw away game result if anything actually changes in examine mode */
3645     if (gameMode == IcsExamining && !newGame) {
3646         gameInfo.result = GameUnfinished;
3647         if (gameInfo.resultDetails != NULL) {
3648             free(gameInfo.resultDetails);
3649             gameInfo.resultDetails = NULL;
3650         }
3651     }
3652     
3653     /* In pausing && IcsExamining mode, we ignore boards coming
3654        in if they are in a different variation than we are. */
3655     if (pauseExamInvalid) return;
3656     if (pausing && gameMode == IcsExamining) {
3657         if (moveNum <= pauseExamForwardMostMove) {
3658             pauseExamInvalid = TRUE;
3659             forwardMostMove = pauseExamForwardMostMove;
3660             return;
3661         }
3662     }
3663     
3664   if (appData.debugMode) {
3665     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3666   }
3667     /* Parse the board */
3668     for (k = 0; k < ranks; k++) {
3669       for (j = 0; j < files; j++)
3670         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3671       if(gameInfo.holdingsWidth > 1) {
3672            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3673            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3674       }
3675     }
3676     CopyBoard(boards[moveNum], board);
3677     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3678     if (moveNum == 0) {
3679         startedFromSetupPosition =
3680           !CompareBoards(board, initialPosition);
3681         if(startedFromSetupPosition)
3682             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3683     }
3684
3685     /* [HGM] Set castling rights. Take the outermost Rooks,
3686        to make it also work for FRC opening positions. Note that board12
3687        is really defective for later FRC positions, as it has no way to
3688        indicate which Rook can castle if they are on the same side of King.
3689        For the initial position we grant rights to the outermost Rooks,
3690        and remember thos rights, and we then copy them on positions
3691        later in an FRC game. This means WB might not recognize castlings with
3692        Rooks that have moved back to their original position as illegal,
3693        but in ICS mode that is not its job anyway.
3694     */
3695     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3696     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3697
3698         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3699             if(board[0][i] == WhiteRook) j = i;
3700         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3702             if(board[0][i] == WhiteRook) j = i;
3703         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3708             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3709         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3710
3711         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3712         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3713             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3714         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3715             if(board[BOARD_HEIGHT-1][k] == bKing)
3716                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3717     } else { int r;
3718         r = boards[moveNum][CASTLING][0] = initialRights[0];
3719         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3720         r = boards[moveNum][CASTLING][1] = initialRights[1];
3721         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3722         r = boards[moveNum][CASTLING][3] = initialRights[3];
3723         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3724         r = boards[moveNum][CASTLING][4] = initialRights[4];
3725         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3726         /* wildcastle kludge: always assume King has rights */
3727         r = boards[moveNum][CASTLING][2] = initialRights[2];
3728         r = boards[moveNum][CASTLING][5] = initialRights[5];
3729     }
3730     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3731     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3732
3733     
3734     if (ics_getting_history == H_GOT_REQ_HEADER ||
3735         ics_getting_history == H_GOT_UNREQ_HEADER) {
3736         /* This was an initial position from a move list, not
3737            the current position */
3738         return;
3739     }
3740     
3741     /* Update currentMove and known move number limits */
3742     newMove = newGame || moveNum > forwardMostMove;
3743
3744     if (newGame) {
3745         forwardMostMove = backwardMostMove = currentMove = moveNum;
3746         if (gameMode == IcsExamining && moveNum == 0) {
3747           /* Workaround for ICS limitation: we are not told the wild
3748              type when starting to examine a game.  But if we ask for
3749              the move list, the move list header will tell us */
3750             ics_getting_history = H_REQUESTED;
3751             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3752             SendToICS(str);
3753         }
3754     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3755                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3756 #if ZIPPY
3757         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3758         /* [HGM] applied this also to an engine that is silently watching        */
3759         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3760             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3761             gameInfo.variant == currentlyInitializedVariant) {
3762           takeback = forwardMostMove - moveNum;
3763           for (i = 0; i < takeback; i++) {
3764             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3765             SendToProgram("undo\n", &first);
3766           }
3767         }
3768 #endif
3769
3770         forwardMostMove = moveNum;
3771         if (!pausing || currentMove > forwardMostMove)
3772           currentMove = forwardMostMove;
3773     } else {
3774         /* New part of history that is not contiguous with old part */ 
3775         if (pausing && gameMode == IcsExamining) {
3776             pauseExamInvalid = TRUE;
3777             forwardMostMove = pauseExamForwardMostMove;
3778             return;
3779         }
3780         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3781 #if ZIPPY
3782             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3783                 // [HGM] when we will receive the move list we now request, it will be
3784                 // fed to the engine from the first move on. So if the engine is not
3785                 // in the initial position now, bring it there.
3786                 InitChessProgram(&first, 0);
3787             }
3788 #endif
3789             ics_getting_history = H_REQUESTED;
3790             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3791             SendToICS(str);
3792         }
3793         forwardMostMove = backwardMostMove = currentMove = moveNum;
3794     }
3795     
3796     /* Update the clocks */
3797     if (strchr(elapsed_time, '.')) {
3798       /* Time is in ms */
3799       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3800       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3801     } else {
3802       /* Time is in seconds */
3803       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3804       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3805     }
3806       
3807
3808 #if ZIPPY
3809     if (appData.zippyPlay && newGame &&
3810         gameMode != IcsObserving && gameMode != IcsIdle &&
3811         gameMode != IcsExamining)
3812       ZippyFirstBoard(moveNum, basetime, increment);
3813 #endif
3814     
3815     /* Put the move on the move list, first converting
3816        to canonical algebraic form. */
3817     if (moveNum > 0) {
3818   if (appData.debugMode) {
3819     if (appData.debugMode) { int f = forwardMostMove;
3820         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3821                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3822                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3823     }
3824     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3825     fprintf(debugFP, "moveNum = %d\n", moveNum);
3826     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3827     setbuf(debugFP, NULL);
3828   }
3829         if (moveNum <= backwardMostMove) {
3830             /* We don't know what the board looked like before
3831                this move.  Punt. */
3832             strcpy(parseList[moveNum - 1], move_str);
3833             strcat(parseList[moveNum - 1], " ");
3834             strcat(parseList[moveNum - 1], elapsed_time);
3835             moveList[moveNum - 1][0] = NULLCHAR;
3836         } else if (strcmp(move_str, "none") == 0) {
3837             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3838             /* Again, we don't know what the board looked like;
3839                this is really the start of the game. */
3840             parseList[moveNum - 1][0] = NULLCHAR;
3841             moveList[moveNum - 1][0] = NULLCHAR;
3842             backwardMostMove = moveNum;
3843             startedFromSetupPosition = TRUE;
3844             fromX = fromY = toX = toY = -1;
3845         } else {
3846           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3847           //                 So we parse the long-algebraic move string in stead of the SAN move
3848           int valid; char buf[MSG_SIZ], *prom;
3849
3850           // str looks something like "Q/a1-a2"; kill the slash
3851           if(str[1] == '/') 
3852                 sprintf(buf, "%c%s", str[0], str+2);
3853           else  strcpy(buf, str); // might be castling
3854           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3855                 strcat(buf, prom); // long move lacks promo specification!
3856           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3857                 if(appData.debugMode) 
3858                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3859                 strcpy(move_str, buf);
3860           }
3861           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3862                                 &fromX, &fromY, &toX, &toY, &promoChar)
3863                || ParseOneMove(buf, moveNum - 1, &moveType,
3864                                 &fromX, &fromY, &toX, &toY, &promoChar);
3865           // end of long SAN patch
3866           if (valid) {
3867             (void) CoordsToAlgebraic(boards[moveNum - 1],
3868                                      PosFlags(moveNum - 1),
3869                                      fromY, fromX, toY, toX, promoChar,
3870                                      parseList[moveNum-1]);
3871             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3872               case MT_NONE:
3873               case MT_STALEMATE:
3874               default:
3875                 break;
3876               case MT_CHECK:
3877                 if(gameInfo.variant != VariantShogi)
3878                     strcat(parseList[moveNum - 1], "+");
3879                 break;
3880               case MT_CHECKMATE:
3881               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3882                 strcat(parseList[moveNum - 1], "#");
3883                 break;
3884             }
3885             strcat(parseList[moveNum - 1], " ");
3886             strcat(parseList[moveNum - 1], elapsed_time);
3887             /* currentMoveString is set as a side-effect of ParseOneMove */
3888             strcpy(moveList[moveNum - 1], currentMoveString);
3889             strcat(moveList[moveNum - 1], "\n");
3890           } else {
3891             /* Move from ICS was illegal!?  Punt. */
3892   if (appData.debugMode) {
3893     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3894     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3895   }
3896             strcpy(parseList[moveNum - 1], move_str);
3897             strcat(parseList[moveNum - 1], " ");
3898             strcat(parseList[moveNum - 1], elapsed_time);
3899             moveList[moveNum - 1][0] = NULLCHAR;
3900             fromX = fromY = toX = toY = -1;
3901           }
3902         }
3903   if (appData.debugMode) {
3904     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3905     setbuf(debugFP, NULL);
3906   }
3907
3908 #if ZIPPY
3909         /* Send move to chess program (BEFORE animating it). */
3910         if (appData.zippyPlay && !newGame && newMove && 
3911            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3912
3913             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3914                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3915                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3916                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3917                             move_str);
3918                     DisplayError(str, 0);
3919                 } else {
3920                     if (first.sendTime) {
3921                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3922                     }
3923                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3924                     if (firstMove && !bookHit) {
3925                         firstMove = FALSE;
3926                         if (first.useColors) {
3927                           SendToProgram(gameMode == IcsPlayingWhite ?
3928                                         "white\ngo\n" :
3929                                         "black\ngo\n", &first);
3930                         } else {
3931                           SendToProgram("go\n", &first);
3932                         }
3933                         first.maybeThinking = TRUE;
3934                     }
3935                 }
3936             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3937               if (moveList[moveNum - 1][0] == NULLCHAR) {
3938                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3939                 DisplayError(str, 0);
3940               } else {
3941                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3942                 SendMoveToProgram(moveNum - 1, &first);
3943               }
3944             }
3945         }
3946 #endif
3947     }
3948
3949     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3950         /* If move comes from a remote source, animate it.  If it
3951            isn't remote, it will have already been animated. */
3952         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3953             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3954         }
3955         if (!pausing && appData.highlightLastMove) {
3956             SetHighlights(fromX, fromY, toX, toY);
3957         }
3958     }
3959     
3960     /* Start the clocks */
3961     whiteFlag = blackFlag = FALSE;
3962     appData.clockMode = !(basetime == 0 && increment == 0);
3963     if (ticking == 0) {
3964       ics_clock_paused = TRUE;
3965       StopClocks();
3966     } else if (ticking == 1) {
3967       ics_clock_paused = FALSE;
3968     }
3969     if (gameMode == IcsIdle ||
3970         relation == RELATION_OBSERVING_STATIC ||
3971         relation == RELATION_EXAMINING ||
3972         ics_clock_paused)
3973       DisplayBothClocks();
3974     else
3975       StartClocks();
3976     
3977     /* Display opponents and material strengths */
3978     if (gameInfo.variant != VariantBughouse &&
3979         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3980         if (tinyLayout || smallLayout) {
3981             if(gameInfo.variant == VariantNormal)
3982                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3984                     basetime, increment);
3985             else
3986                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3987                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3988                     basetime, increment, (int) gameInfo.variant);
3989         } else {
3990             if(gameInfo.variant == VariantNormal)
3991                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3992                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3993                     basetime, increment);
3994             else
3995                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3996                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3997                     basetime, increment, VariantName(gameInfo.variant));
3998         }
3999         DisplayTitle(str);
4000   if (appData.debugMode) {
4001     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4002   }
4003     }
4004
4005    
4006     /* Display the board */
4007     if (!pausing && !appData.noGUI) {
4008       
4009       if (appData.premove)
4010           if (!gotPremove || 
4011              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4012              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4013               ClearPremoveHighlights();
4014
4015       DrawPosition(FALSE, boards[currentMove]);
4016       DisplayMove(moveNum - 1);
4017       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4018             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4019               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4020         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4021       }
4022     }
4023
4024     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4025 #if ZIPPY
4026     if(bookHit) { // [HGM] book: simulate book reply
4027         static char bookMove[MSG_SIZ]; // a bit generous?
4028
4029         programStats.nodes = programStats.depth = programStats.time = 
4030         programStats.score = programStats.got_only_move = 0;
4031         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4032
4033         strcpy(bookMove, "move ");
4034         strcat(bookMove, bookHit);
4035         HandleMachineMove(bookMove, &first);
4036     }
4037 #endif
4038 }
4039
4040 void
4041 GetMoveListEvent()
4042 {
4043     char buf[MSG_SIZ];
4044     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4045         ics_getting_history = H_REQUESTED;
4046         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4047         SendToICS(buf);
4048     }
4049 }
4050
4051 void
4052 AnalysisPeriodicEvent(force)
4053      int force;
4054 {
4055     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4056          && !force) || !appData.periodicUpdates)
4057       return;
4058
4059     /* Send . command to Crafty to collect stats */
4060     SendToProgram(".\n", &first);
4061
4062     /* Don't send another until we get a response (this makes
4063        us stop sending to old Crafty's which don't understand
4064        the "." command (sending illegal cmds resets node count & time,
4065        which looks bad)) */
4066     programStats.ok_to_send = 0;
4067 }
4068
4069 void ics_update_width(new_width)
4070         int new_width;
4071 {
4072         ics_printf("set width %d\n", new_width);
4073 }
4074
4075 void
4076 SendMoveToProgram(moveNum, cps)
4077      int moveNum;
4078      ChessProgramState *cps;
4079 {
4080     char buf[MSG_SIZ];
4081
4082     if (cps->useUsermove) {
4083       SendToProgram("usermove ", cps);
4084     }
4085     if (cps->useSAN) {
4086       char *space;
4087       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4088         int len = space - parseList[moveNum];
4089         memcpy(buf, parseList[moveNum], len);
4090         buf[len++] = '\n';
4091         buf[len] = NULLCHAR;
4092       } else {
4093         sprintf(buf, "%s\n", parseList[moveNum]);
4094       }
4095       SendToProgram(buf, cps);
4096     } else {
4097       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4098         AlphaRank(moveList[moveNum], 4);
4099         SendToProgram(moveList[moveNum], cps);
4100         AlphaRank(moveList[moveNum], 4); // and back
4101       } else
4102       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4103        * the engine. It would be nice to have a better way to identify castle 
4104        * moves here. */
4105       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4106                                                                          && cps->useOOCastle) {
4107         int fromX = moveList[moveNum][0] - AAA; 
4108         int fromY = moveList[moveNum][1] - ONE;
4109         int toX = moveList[moveNum][2] - AAA; 
4110         int toY = moveList[moveNum][3] - ONE;
4111         if((boards[moveNum][fromY][fromX] == WhiteKing 
4112             && boards[moveNum][toY][toX] == WhiteRook)
4113            || (boards[moveNum][fromY][fromX] == BlackKing 
4114                && boards[moveNum][toY][toX] == BlackRook)) {
4115           if(toX > fromX) SendToProgram("O-O\n", cps);
4116           else SendToProgram("O-O-O\n", cps);
4117         }
4118         else SendToProgram(moveList[moveNum], cps);
4119       }
4120       else SendToProgram(moveList[moveNum], cps);
4121       /* End of additions by Tord */
4122     }
4123
4124     /* [HGM] setting up the opening has brought engine in force mode! */
4125     /*       Send 'go' if we are in a mode where machine should play. */
4126     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4127         (gameMode == TwoMachinesPlay   ||
4128 #ifdef ZIPPY
4129          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4130 #endif
4131          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4132         SendToProgram("go\n", cps);
4133   if (appData.debugMode) {
4134     fprintf(debugFP, "(extra)\n");
4135   }
4136     }
4137     setboardSpoiledMachineBlack = 0;
4138 }
4139
4140 void
4141 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4142      ChessMove moveType;
4143      int fromX, fromY, toX, toY;
4144 {
4145     char user_move[MSG_SIZ];
4146
4147     switch (moveType) {
4148       default:
4149         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4150                 (int)moveType, fromX, fromY, toX, toY);
4151         DisplayError(user_move + strlen("say "), 0);
4152         break;
4153       case WhiteKingSideCastle:
4154       case BlackKingSideCastle:
4155       case WhiteQueenSideCastleWild:
4156       case BlackQueenSideCastleWild:
4157       /* PUSH Fabien */
4158       case WhiteHSideCastleFR:
4159       case BlackHSideCastleFR:
4160       /* POP Fabien */
4161         sprintf(user_move, "o-o\n");
4162         break;
4163       case WhiteQueenSideCastle:
4164       case BlackQueenSideCastle:
4165       case WhiteKingSideCastleWild:
4166       case BlackKingSideCastleWild:
4167       /* PUSH Fabien */
4168       case WhiteASideCastleFR:
4169       case BlackASideCastleFR:
4170       /* POP Fabien */
4171         sprintf(user_move, "o-o-o\n");
4172         break;
4173       case WhitePromotionQueen:
4174       case BlackPromotionQueen:
4175       case WhitePromotionRook:
4176       case BlackPromotionRook:
4177       case WhitePromotionBishop:
4178       case BlackPromotionBishop:
4179       case WhitePromotionKnight:
4180       case BlackPromotionKnight:
4181       case WhitePromotionKing:
4182       case BlackPromotionKing:
4183       case WhitePromotionChancellor:
4184       case BlackPromotionChancellor:
4185       case WhitePromotionArchbishop:
4186       case BlackPromotionArchbishop:
4187         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4188             sprintf(user_move, "%c%c%c%c=%c\n",
4189                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4190                 PieceToChar(WhiteFerz));
4191         else if(gameInfo.variant == VariantGreat)
4192             sprintf(user_move, "%c%c%c%c=%c\n",
4193                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4194                 PieceToChar(WhiteMan));
4195         else
4196             sprintf(user_move, "%c%c%c%c=%c\n",
4197                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4198                 PieceToChar(PromoPiece(moveType)));
4199         break;
4200       case WhiteDrop:
4201       case BlackDrop:
4202         sprintf(user_move, "%c@%c%c\n",
4203                 ToUpper(PieceToChar((ChessSquare) fromX)),
4204                 AAA + toX, ONE + toY);
4205         break;
4206       case NormalMove:
4207       case WhiteCapturesEnPassant:
4208       case BlackCapturesEnPassant:
4209       case IllegalMove:  /* could be a variant we don't quite understand */
4210         sprintf(user_move, "%c%c%c%c\n",
4211                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4212         break;
4213     }
4214     SendToICS(user_move);
4215     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4216         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4217 }
4218
4219 void
4220 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4221      int rf, ff, rt, ft;
4222      char promoChar;
4223      char move[7];
4224 {
4225     if (rf == DROP_RANK) {
4226         sprintf(move, "%c@%c%c\n",
4227                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4228     } else {
4229         if (promoChar == 'x' || promoChar == NULLCHAR) {
4230             sprintf(move, "%c%c%c%c\n",
4231                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4232         } else {
4233             sprintf(move, "%c%c%c%c%c\n",
4234                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4235         }
4236     }
4237 }
4238
4239 void
4240 ProcessICSInitScript(f)
4241      FILE *f;
4242 {
4243     char buf[MSG_SIZ];
4244
4245     while (fgets(buf, MSG_SIZ, f)) {
4246         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4247     }
4248
4249     fclose(f);
4250 }
4251
4252
4253 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4254 void
4255 AlphaRank(char *move, int n)
4256 {
4257 //    char *p = move, c; int x, y;
4258
4259     if (appData.debugMode) {
4260         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4261     }
4262
4263     if(move[1]=='*' && 
4264        move[2]>='0' && move[2]<='9' &&
4265        move[3]>='a' && move[3]<='x'    ) {
4266         move[1] = '@';
4267         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4268         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4269     } else
4270     if(move[0]>='0' && move[0]<='9' &&
4271        move[1]>='a' && move[1]<='x' &&
4272        move[2]>='0' && move[2]<='9' &&
4273        move[3]>='a' && move[3]<='x'    ) {
4274         /* input move, Shogi -> normal */
4275         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4276         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4277         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4278         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4279     } else
4280     if(move[1]=='@' &&
4281        move[3]>='0' && move[3]<='9' &&
4282        move[2]>='a' && move[2]<='x'    ) {
4283         move[1] = '*';
4284         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4285         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4286     } else
4287     if(
4288        move[0]>='a' && move[0]<='x' &&
4289        move[3]>='0' && move[3]<='9' &&
4290        move[2]>='a' && move[2]<='x'    ) {
4291          /* output move, normal -> Shogi */
4292         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4293         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4294         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4295         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4296         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4297     }
4298     if (appData.debugMode) {
4299         fprintf(debugFP, "   out = '%s'\n", move);
4300     }
4301 }
4302
4303 /* Parser for moves from gnuchess, ICS, or user typein box */
4304 Boolean
4305 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4306      char *move;
4307      int moveNum;
4308      ChessMove *moveType;
4309      int *fromX, *fromY, *toX, *toY;
4310      char *promoChar;
4311 {       
4312     if (appData.debugMode) {
4313         fprintf(debugFP, "move to parse: %s\n", move);
4314     }
4315     *moveType = yylexstr(moveNum, move);
4316
4317     switch (*moveType) {
4318       case WhitePromotionChancellor:
4319       case BlackPromotionChancellor:
4320       case WhitePromotionArchbishop:
4321       case BlackPromotionArchbishop:
4322       case WhitePromotionQueen:
4323       case BlackPromotionQueen:
4324       case WhitePromotionRook:
4325       case BlackPromotionRook:
4326       case WhitePromotionBishop:
4327       case BlackPromotionBishop:
4328       case WhitePromotionKnight:
4329       case BlackPromotionKnight:
4330       case WhitePromotionKing:
4331       case BlackPromotionKing:
4332       case NormalMove:
4333       case WhiteCapturesEnPassant:
4334       case BlackCapturesEnPassant:
4335       case WhiteKingSideCastle:
4336       case WhiteQueenSideCastle:
4337       case BlackKingSideCastle:
4338       case BlackQueenSideCastle:
4339       case WhiteKingSideCastleWild:
4340       case WhiteQueenSideCastleWild:
4341       case BlackKingSideCastleWild:
4342       case BlackQueenSideCastleWild:
4343       /* Code added by Tord: */
4344       case WhiteHSideCastleFR:
4345       case WhiteASideCastleFR:
4346       case BlackHSideCastleFR:
4347       case BlackASideCastleFR:
4348       /* End of code added by Tord */
4349       case IllegalMove:         /* bug or odd chess variant */
4350         *fromX = currentMoveString[0] - AAA;
4351         *fromY = currentMoveString[1] - ONE;
4352         *toX = currentMoveString[2] - AAA;
4353         *toY = currentMoveString[3] - ONE;
4354         *promoChar = currentMoveString[4];
4355         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4356             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4357     if (appData.debugMode) {
4358         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4359     }
4360             *fromX = *fromY = *toX = *toY = 0;
4361             return FALSE;
4362         }
4363         if (appData.testLegality) {
4364           return (*moveType != IllegalMove);
4365         } else {
4366           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4367                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4368         }
4369
4370       case WhiteDrop:
4371       case BlackDrop:
4372         *fromX = *moveType == WhiteDrop ?
4373           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4374           (int) CharToPiece(ToLower(currentMoveString[0]));
4375         *fromY = DROP_RANK;
4376         *toX = currentMoveString[2] - AAA;
4377         *toY = currentMoveString[3] - ONE;
4378         *promoChar = NULLCHAR;
4379         return TRUE;
4380
4381       case AmbiguousMove:
4382       case ImpossibleMove:
4383       case (ChessMove) 0:       /* end of file */
4384       case ElapsedTime:
4385       case Comment:
4386       case PGNTag:
4387       case NAG:
4388       case WhiteWins:
4389       case BlackWins:
4390       case GameIsDrawn:
4391       default:
4392     if (appData.debugMode) {
4393         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4394     }
4395         /* bug? */
4396         *fromX = *fromY = *toX = *toY = 0;
4397         *promoChar = NULLCHAR;
4398         return FALSE;
4399     }
4400 }
4401
4402
4403 void
4404 ParsePV(char *pv)
4405 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4406   int fromX, fromY, toX, toY; char promoChar;
4407   ChessMove moveType;
4408   Boolean valid;
4409   int nr = 0;
4410
4411   endPV = forwardMostMove;
4412   do {
4413     while(*pv == ' ') pv++;
4414     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4415     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4416 if(appData.debugMode){
4417 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4418 }
4419     if(!valid && nr == 0 &&
4420        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4421         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4422     }
4423     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4424     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4425     nr++;
4426     if(endPV+1 > framePtr) break; // no space, truncate
4427     if(!valid) break;
4428     endPV++;
4429     CopyBoard(boards[endPV], boards[endPV-1]);
4430     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4431     moveList[endPV-1][0] = fromX + AAA;
4432     moveList[endPV-1][1] = fromY + ONE;
4433     moveList[endPV-1][2] = toX + AAA;
4434     moveList[endPV-1][3] = toY + ONE;
4435     parseList[endPV-1][0] = NULLCHAR;
4436   } while(valid);
4437   currentMove = endPV;
4438   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4439   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4440                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4441   DrawPosition(TRUE, boards[currentMove]);
4442 }
4443
4444 static int lastX, lastY;
4445
4446 Boolean
4447 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4448 {
4449         int startPV;
4450
4451         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4452         lastX = x; lastY = y;
4453         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4454         startPV = index;
4455       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4456       index = startPV;
4457         while(buf[index] && buf[index] != '\n') index++;
4458         buf[index] = 0;
4459         ParsePV(buf+startPV);
4460         *start = startPV; *end = index-1;
4461         return TRUE;
4462 }
4463
4464 Boolean
4465 LoadPV(int x, int y)
4466 { // called on right mouse click to load PV
4467   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4468   lastX = x; lastY = y;
4469   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4470   return TRUE;
4471 }
4472
4473 void
4474 UnLoadPV()
4475 {
4476   if(endPV < 0) return;
4477   endPV = -1;
4478   currentMove = forwardMostMove;
4479   ClearPremoveHighlights();
4480   DrawPosition(TRUE, boards[currentMove]);
4481 }
4482
4483 void
4484 MovePV(int x, int y, int h)
4485 { // step through PV based on mouse coordinates (called on mouse move)
4486   int margin = h>>3, step = 0;
4487
4488   if(endPV < 0) return;
4489   // we must somehow check if right button is still down (might be released off board!)
4490   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4491   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4492   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4493   if(!step) return;
4494   lastX = x; lastY = y;
4495   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4496   currentMove += step;
4497   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4498   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4499                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4500   DrawPosition(FALSE, boards[currentMove]);
4501 }
4502
4503
4504 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4505 // All positions will have equal probability, but the current method will not provide a unique
4506 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4507 #define DARK 1
4508 #define LITE 2
4509 #define ANY 3
4510
4511 int squaresLeft[4];
4512 int piecesLeft[(int)BlackPawn];
4513 int seed, nrOfShuffles;
4514
4515 void GetPositionNumber()
4516 {       // sets global variable seed
4517         int i;
4518
4519         seed = appData.defaultFrcPosition;
4520         if(seed < 0) { // randomize based on time for negative FRC position numbers
4521                 for(i=0; i<50; i++) seed += random();
4522                 seed = random() ^ random() >> 8 ^ random() << 8;
4523                 if(seed<0) seed = -seed;
4524         }
4525 }
4526
4527 int put(Board board, int pieceType, int rank, int n, int shade)
4528 // put the piece on the (n-1)-th empty squares of the given shade
4529 {
4530         int i;
4531
4532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4533                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4534                         board[rank][i] = (ChessSquare) pieceType;
4535                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4536                         squaresLeft[ANY]--;
4537                         piecesLeft[pieceType]--; 
4538                         return i;
4539                 }
4540         }
4541         return -1;
4542 }
4543
4544
4545 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4546 // calculate where the next piece goes, (any empty square), and put it there
4547 {
4548         int i;
4549
4550         i = seed % squaresLeft[shade];
4551         nrOfShuffles *= squaresLeft[shade];
4552         seed /= squaresLeft[shade];
4553         put(board, pieceType, rank, i, shade);
4554 }
4555
4556 void AddTwoPieces(Board board, int pieceType, int rank)
4557 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4558 {
4559         int i, n=squaresLeft[ANY], j=n-1, k;
4560
4561         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4562         i = seed % k;  // pick one
4563         nrOfShuffles *= k;
4564         seed /= k;
4565         while(i >= j) i -= j--;
4566         j = n - 1 - j; i += j;
4567         put(board, pieceType, rank, j, ANY);
4568         put(board, pieceType, rank, i, ANY);
4569 }
4570
4571 void SetUpShuffle(Board board, int number)
4572 {
4573         int i, p, first=1;
4574
4575         GetPositionNumber(); nrOfShuffles = 1;
4576
4577         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4578         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4579         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4580
4581         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4582
4583         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4584             p = (int) board[0][i];
4585             if(p < (int) BlackPawn) piecesLeft[p] ++;
4586             board[0][i] = EmptySquare;
4587         }
4588
4589         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4590             // shuffles restricted to allow normal castling put KRR first
4591             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4592                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4593             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4594                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4595             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4596                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4597             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4598                 put(board, WhiteRook, 0, 0, ANY);
4599             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4600         }
4601
4602         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4603             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4604             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4605                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4606                 while(piecesLeft[p] >= 2) {
4607                     AddOnePiece(board, p, 0, LITE);
4608                     AddOnePiece(board, p, 0, DARK);
4609                 }
4610                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4611             }
4612
4613         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4614             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4615             // but we leave King and Rooks for last, to possibly obey FRC restriction
4616             if(p == (int)WhiteRook) continue;
4617             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4618             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4619         }
4620
4621         // now everything is placed, except perhaps King (Unicorn) and Rooks
4622
4623         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4624             // Last King gets castling rights
4625             while(piecesLeft[(int)WhiteUnicorn]) {
4626                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4627                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4628             }
4629
4630             while(piecesLeft[(int)WhiteKing]) {
4631                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4632                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4633             }
4634
4635
4636         } else {
4637             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4638             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4639         }
4640
4641         // Only Rooks can be left; simply place them all
4642         while(piecesLeft[(int)WhiteRook]) {
4643                 i = put(board, WhiteRook, 0, 0, ANY);
4644                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4645                         if(first) {
4646                                 first=0;
4647                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4648                         }
4649                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4650                 }
4651         }
4652         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4653             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4654         }
4655
4656         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4657 }
4658
4659 int SetCharTable( char *table, const char * map )
4660 /* [HGM] moved here from winboard.c because of its general usefulness */
4661 /*       Basically a safe strcpy that uses the last character as King */
4662 {
4663     int result = FALSE; int NrPieces;
4664
4665     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4666                     && NrPieces >= 12 && !(NrPieces&1)) {
4667         int i; /* [HGM] Accept even length from 12 to 34 */
4668
4669         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4670         for( i=0; i<NrPieces/2-1; i++ ) {
4671             table[i] = map[i];
4672             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4673         }
4674         table[(int) WhiteKing]  = map[NrPieces/2-1];
4675         table[(int) BlackKing]  = map[NrPieces-1];
4676
4677         result = TRUE;
4678     }
4679
4680     return result;
4681 }
4682
4683 void Prelude(Board board)
4684 {       // [HGM] superchess: random selection of exo-pieces
4685         int i, j, k; ChessSquare p; 
4686         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4687
4688         GetPositionNumber(); // use FRC position number
4689
4690         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4691             SetCharTable(pieceToChar, appData.pieceToCharTable);
4692             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4693                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4694         }
4695
4696         j = seed%4;                 seed /= 4; 
4697         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4700         j = seed%3 + (seed%3 >= j); seed /= 3; 
4701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4704         j = seed%3;                 seed /= 3; 
4705         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4708         j = seed%2 + (seed%2 >= j); seed /= 2; 
4709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4712         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4713         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4714         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4715         put(board, exoPieces[0],    0, 0, ANY);
4716         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4717 }
4718
4719 void
4720 InitPosition(redraw)
4721      int redraw;
4722 {
4723     ChessSquare (* pieces)[BOARD_FILES];
4724     int i, j, pawnRow, overrule,
4725     oldx = gameInfo.boardWidth,
4726     oldy = gameInfo.boardHeight,
4727     oldh = gameInfo.holdingsWidth,
4728     oldv = gameInfo.variant;
4729
4730     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4731
4732     /* [AS] Initialize pv info list [HGM] and game status */
4733     {
4734         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4735             pvInfoList[i].depth = 0;
4736             boards[i][EP_STATUS] = EP_NONE;
4737             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4738         }
4739
4740         initialRulePlies = 0; /* 50-move counter start */
4741
4742         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4743         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4744     }
4745
4746     
4747     /* [HGM] logic here is completely changed. In stead of full positions */
4748     /* the initialized data only consist of the two backranks. The switch */
4749     /* selects which one we will use, which is than copied to the Board   */
4750     /* initialPosition, which for the rest is initialized by Pawns and    */
4751     /* empty squares. This initial position is then copied to boards[0],  */
4752     /* possibly after shuffling, so that it remains available.            */
4753
4754     gameInfo.holdingsWidth = 0; /* default board sizes */
4755     gameInfo.boardWidth    = 8;
4756     gameInfo.boardHeight   = 8;
4757     gameInfo.holdingsSize  = 0;
4758     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4759     for(i=0; i<BOARD_FILES-2; i++)
4760       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4761     initialPosition[EP_STATUS] = EP_NONE;
4762     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4763
4764     switch (gameInfo.variant) {
4765     case VariantFischeRandom:
4766       shuffleOpenings = TRUE;
4767     default:
4768       pieces = FIDEArray;
4769       break;
4770     case VariantShatranj:
4771       pieces = ShatranjArray;
4772       nrCastlingRights = 0;
4773       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4774       break;
4775     case VariantTwoKings:
4776       pieces = twoKingsArray;
4777       break;
4778     case VariantCapaRandom:
4779       shuffleOpenings = TRUE;
4780     case VariantCapablanca:
4781       pieces = CapablancaArray;
4782       gameInfo.boardWidth = 10;
4783       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4784       break;
4785     case VariantGothic:
4786       pieces = GothicArray;
4787       gameInfo.boardWidth = 10;
4788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4789       break;
4790     case VariantJanus:
4791       pieces = JanusArray;
4792       gameInfo.boardWidth = 10;
4793       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4794       nrCastlingRights = 6;
4795         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4796         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4797         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4798         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4799         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4800         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4801       break;
4802     case VariantFalcon:
4803       pieces = FalconArray;
4804       gameInfo.boardWidth = 10;
4805       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4806       break;
4807     case VariantXiangqi:
4808       pieces = XiangqiArray;
4809       gameInfo.boardWidth  = 9;
4810       gameInfo.boardHeight = 10;
4811       nrCastlingRights = 0;
4812       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4813       break;
4814     case VariantShogi:
4815       pieces = ShogiArray;
4816       gameInfo.boardWidth  = 9;
4817       gameInfo.boardHeight = 9;
4818       gameInfo.holdingsSize = 7;
4819       nrCastlingRights = 0;
4820       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4821       break;
4822     case VariantCourier:
4823       pieces = CourierArray;
4824       gameInfo.boardWidth  = 12;
4825       nrCastlingRights = 0;
4826       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4827       break;
4828     case VariantKnightmate:
4829       pieces = KnightmateArray;
4830       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4831       break;
4832     case VariantFairy:
4833       pieces = fairyArray;
4834       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4835       break;
4836     case VariantGreat:
4837       pieces = GreatArray;
4838       gameInfo.boardWidth = 10;
4839       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4840       gameInfo.holdingsSize = 8;
4841       break;
4842     case VariantSuper:
4843       pieces = FIDEArray;
4844       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4845       gameInfo.holdingsSize = 8;
4846       startedFromSetupPosition = TRUE;
4847       break;
4848     case VariantCrazyhouse:
4849     case VariantBughouse:
4850       pieces = FIDEArray;
4851       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4852       gameInfo.holdingsSize = 5;
4853       break;
4854     case VariantWildCastle:
4855       pieces = FIDEArray;
4856       /* !!?shuffle with kings guaranteed to be on d or e file */
4857       shuffleOpenings = 1;
4858       break;
4859     case VariantNoCastle:
4860       pieces = FIDEArray;
4861       nrCastlingRights = 0;
4862       /* !!?unconstrained back-rank shuffle */
4863       shuffleOpenings = 1;
4864       break;
4865     }
4866
4867     overrule = 0;
4868     if(appData.NrFiles >= 0) {
4869         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4870         gameInfo.boardWidth = appData.NrFiles;
4871     }
4872     if(appData.NrRanks >= 0) {
4873         gameInfo.boardHeight = appData.NrRanks;
4874     }
4875     if(appData.holdingsSize >= 0) {
4876         i = appData.holdingsSize;
4877         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4878         gameInfo.holdingsSize = i;
4879     }
4880     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4881     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4882         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4883
4884     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4885     if(pawnRow < 1) pawnRow = 1;
4886
4887     /* User pieceToChar list overrules defaults */
4888     if(appData.pieceToCharTable != NULL)
4889         SetCharTable(pieceToChar, appData.pieceToCharTable);
4890
4891     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4892
4893         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4894             s = (ChessSquare) 0; /* account holding counts in guard band */
4895         for( i=0; i<BOARD_HEIGHT; i++ )
4896             initialPosition[i][j] = s;
4897
4898         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4899         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4900         initialPosition[pawnRow][j] = WhitePawn;
4901         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4902         if(gameInfo.variant == VariantXiangqi) {
4903             if(j&1) {
4904                 initialPosition[pawnRow][j] = 
4905                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4906                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4907                    initialPosition[2][j] = WhiteCannon;
4908                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4909                 }
4910             }
4911         }
4912         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4913     }
4914     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4915
4916             j=BOARD_LEFT+1;
4917             initialPosition[1][j] = WhiteBishop;
4918             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4919             j=BOARD_RGHT-2;
4920             initialPosition[1][j] = WhiteRook;
4921             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4922     }
4923
4924     if( nrCastlingRights == -1) {
4925         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4926         /*       This sets default castling rights from none to normal corners   */
4927         /* Variants with other castling rights must set them themselves above    */
4928         nrCastlingRights = 6;
4929        
4930         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4931         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4932         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4933         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4934         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4935         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4936      }
4937
4938      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4939      if(gameInfo.variant == VariantGreat) { // promotion commoners
4940         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4941         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4942         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4943         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4944      }
4945   if (appData.debugMode) {
4946     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4947   }
4948     if(shuffleOpenings) {
4949         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4950         startedFromSetupPosition = TRUE;
4951     }
4952     if(startedFromPositionFile) {
4953       /* [HGM] loadPos: use PositionFile for every new game */
4954       CopyBoard(initialPosition, filePosition);
4955       for(i=0; i<nrCastlingRights; i++)
4956           initialRights[i] = filePosition[CASTLING][i];
4957       startedFromSetupPosition = TRUE;
4958     }
4959
4960     CopyBoard(boards[0], initialPosition);
4961
4962     if(oldx != gameInfo.boardWidth ||
4963        oldy != gameInfo.boardHeight ||
4964        oldh != gameInfo.holdingsWidth
4965 #ifdef GOTHIC
4966        || oldv == VariantGothic ||        // For licensing popups
4967        gameInfo.variant == VariantGothic
4968 #endif
4969 #ifdef FALCON
4970        || oldv == VariantFalcon ||
4971        gameInfo.variant == VariantFalcon
4972 #endif
4973                                          )
4974             InitDrawingSizes(-2 ,0);
4975
4976     if (redraw)
4977       DrawPosition(TRUE, boards[currentMove]);
4978 }
4979
4980 void
4981 SendBoard(cps, moveNum)
4982      ChessProgramState *cps;
4983      int moveNum;
4984 {
4985     char message[MSG_SIZ];
4986     
4987     if (cps->useSetboard) {
4988       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4989       sprintf(message, "setboard %s\n", fen);
4990       SendToProgram(message, cps);
4991       free(fen);
4992
4993     } else {
4994       ChessSquare *bp;
4995       int i, j;
4996       /* Kludge to set black to move, avoiding the troublesome and now
4997        * deprecated "black" command.
4998        */
4999       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5000
5001       SendToProgram("edit\n", cps);
5002       SendToProgram("#\n", cps);
5003       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5004         bp = &boards[moveNum][i][BOARD_LEFT];
5005         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5006           if ((int) *bp < (int) BlackPawn) {
5007             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5008                     AAA + j, ONE + i);
5009             if(message[0] == '+' || message[0] == '~') {
5010                 sprintf(message, "%c%c%c+\n",
5011                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5012                         AAA + j, ONE + i);
5013             }
5014             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5015                 message[1] = BOARD_RGHT   - 1 - j + '1';
5016                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5017             }
5018             SendToProgram(message, cps);
5019           }
5020         }
5021       }
5022     
5023       SendToProgram("c\n", cps);
5024       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5025         bp = &boards[moveNum][i][BOARD_LEFT];
5026         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5027           if (((int) *bp != (int) EmptySquare)
5028               && ((int) *bp >= (int) BlackPawn)) {
5029             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5030                     AAA + j, ONE + i);
5031             if(message[0] == '+' || message[0] == '~') {
5032                 sprintf(message, "%c%c%c+\n",
5033                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5034                         AAA + j, ONE + i);
5035             }
5036             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5037                 message[1] = BOARD_RGHT   - 1 - j + '1';
5038                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5039             }
5040             SendToProgram(message, cps);
5041           }
5042         }
5043       }
5044     
5045       SendToProgram(".\n", cps);
5046     }
5047     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5048 }
5049
5050 int
5051 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5052 {
5053     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5054     /* [HGM] add Shogi promotions */
5055     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5056     ChessSquare piece;
5057     ChessMove moveType;
5058     Boolean premove;
5059
5060     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5061     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5062
5063     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5064       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5065         return FALSE;
5066
5067     piece = boards[currentMove][fromY][fromX];
5068     if(gameInfo.variant == VariantShogi) {
5069         promotionZoneSize = 3;
5070         highestPromotingPiece = (int)WhiteFerz;
5071     }
5072
5073     // next weed out all moves that do not touch the promotion zone at all
5074     if((int)piece >= BlackPawn) {
5075         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5076              return FALSE;
5077         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5078     } else {
5079         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5080            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5081     }
5082
5083     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5084
5085     // weed out mandatory Shogi promotions
5086     if(gameInfo.variant == VariantShogi) {
5087         if(piece >= BlackPawn) {
5088             if(toY == 0 && piece == BlackPawn ||
5089                toY == 0 && piece == BlackQueen ||
5090                toY <= 1 && piece == BlackKnight) {
5091                 *promoChoice = '+';
5092                 return FALSE;
5093             }
5094         } else {
5095             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5096                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5097                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5098                 *promoChoice = '+';
5099                 return FALSE;
5100             }
5101         }
5102     }
5103
5104     // weed out obviously illegal Pawn moves
5105     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5106         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5107         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5108         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5109         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5110         // note we are not allowed to test for valid (non-)capture, due to premove
5111     }
5112
5113     // we either have a choice what to promote to, or (in Shogi) whether to promote
5114     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5115         *promoChoice = PieceToChar(BlackFerz);  // no choice
5116         return FALSE;
5117     }
5118     if(appData.alwaysPromoteToQueen) { // predetermined
5119         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5120              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5121         else *promoChoice = PieceToChar(BlackQueen);
5122         return FALSE;
5123     }
5124
5125     // suppress promotion popup on illegal moves that are not premoves
5126     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5127               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5128     if(appData.testLegality && !premove) {
5129         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5130                         fromY, fromX, toY, toX, NULLCHAR);
5131         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5132            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5133             return FALSE;
5134     }
5135
5136     return TRUE;
5137 }
5138
5139 int
5140 InPalace(row, column)
5141      int row, column;
5142 {   /* [HGM] for Xiangqi */
5143     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5144          column < (BOARD_WIDTH + 4)/2 &&
5145          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5146     return FALSE;
5147 }
5148
5149 int
5150 PieceForSquare (x, y)
5151      int x;
5152      int y;
5153 {
5154   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5155      return -1;
5156   else
5157      return boards[currentMove][y][x];
5158 }
5159
5160 int
5161 OKToStartUserMove(x, y)
5162      int x, y;
5163 {
5164     ChessSquare from_piece;
5165     int white_piece;
5166
5167     if (matchMode) return FALSE;
5168     if (gameMode == EditPosition) return TRUE;
5169
5170     if (x >= 0 && y >= 0)
5171       from_piece = boards[currentMove][y][x];
5172     else
5173       from_piece = EmptySquare;
5174
5175     if (from_piece == EmptySquare) return FALSE;
5176
5177     white_piece = (int)from_piece >= (int)WhitePawn &&
5178       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5179
5180     switch (gameMode) {
5181       case PlayFromGameFile:
5182       case AnalyzeFile:
5183       case TwoMachinesPlay:
5184       case EndOfGame:
5185         return FALSE;
5186
5187       case IcsObserving:
5188       case IcsIdle:
5189         return FALSE;
5190
5191       case MachinePlaysWhite:
5192       case IcsPlayingBlack:
5193         if (appData.zippyPlay) return FALSE;
5194         if (white_piece) {
5195             DisplayMoveError(_("You are playing Black"));
5196             return FALSE;
5197         }
5198         break;
5199
5200       case MachinePlaysBlack:
5201       case IcsPlayingWhite:
5202         if (appData.zippyPlay) return FALSE;
5203         if (!white_piece) {
5204             DisplayMoveError(_("You are playing White"));
5205             return FALSE;
5206         }
5207         break;
5208
5209       case EditGame:
5210         if (!white_piece && WhiteOnMove(currentMove)) {
5211             DisplayMoveError(_("It is White's turn"));
5212             return FALSE;
5213         }           
5214         if (white_piece && !WhiteOnMove(currentMove)) {
5215             DisplayMoveError(_("It is Black's turn"));
5216             return FALSE;
5217         }           
5218         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5219             /* Editing correspondence game history */
5220             /* Could disallow this or prompt for confirmation */
5221             cmailOldMove = -1;
5222         }
5223         break;
5224
5225       case BeginningOfGame:
5226         if (appData.icsActive) return FALSE;
5227         if (!appData.noChessProgram) {
5228             if (!white_piece) {
5229                 DisplayMoveError(_("You are playing White"));
5230                 return FALSE;
5231             }
5232         }
5233         break;
5234         
5235       case Training:
5236         if (!white_piece && WhiteOnMove(currentMove)) {
5237             DisplayMoveError(_("It is White's turn"));
5238             return FALSE;
5239         }           
5240         if (white_piece && !WhiteOnMove(currentMove)) {
5241             DisplayMoveError(_("It is Black's turn"));
5242             return FALSE;
5243         }           
5244         break;
5245
5246       default:
5247       case IcsExamining:
5248         break;
5249     }
5250     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5251         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5252         && gameMode != AnalyzeFile && gameMode != Training) {
5253         DisplayMoveError(_("Displayed position is not current"));
5254         return FALSE;
5255     }
5256     return TRUE;
5257 }
5258
5259 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5260 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5261 int lastLoadGameUseList = FALSE;
5262 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5263 ChessMove lastLoadGameStart = (ChessMove) 0;
5264
5265 ChessMove
5266 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5267      int fromX, fromY, toX, toY;
5268      int promoChar;
5269      Boolean captureOwn;
5270 {
5271     ChessMove moveType;
5272     ChessSquare pdown, pup;
5273
5274     /* Check if the user is playing in turn.  This is complicated because we
5275        let the user "pick up" a piece before it is his turn.  So the piece he
5276        tried to pick up may have been captured by the time he puts it down!
5277        Therefore we use the color the user is supposed to be playing in this
5278        test, not the color of the piece that is currently on the starting
5279        square---except in EditGame mode, where the user is playing both
5280        sides; fortunately there the capture race can't happen.  (It can
5281        now happen in IcsExamining mode, but that's just too bad.  The user
5282        will get a somewhat confusing message in that case.)
5283        */
5284
5285     switch (gameMode) {
5286       case PlayFromGameFile:
5287       case AnalyzeFile:
5288       case TwoMachinesPlay:
5289       case EndOfGame:
5290       case IcsObserving:
5291       case IcsIdle:
5292         /* We switched into a game mode where moves are not accepted,
5293            perhaps while the mouse button was down. */
5294         return ImpossibleMove;
5295
5296       case MachinePlaysWhite:
5297         /* User is moving for Black */
5298         if (WhiteOnMove(currentMove)) {
5299             DisplayMoveError(_("It is White's turn"));
5300             return ImpossibleMove;
5301         }
5302         break;
5303
5304       case MachinePlaysBlack:
5305         /* User is moving for White */
5306         if (!WhiteOnMove(currentMove)) {
5307             DisplayMoveError(_("It is Black's turn"));
5308             return ImpossibleMove;
5309         }
5310         break;
5311
5312       case EditGame:
5313       case IcsExamining:
5314       case BeginningOfGame:
5315       case AnalyzeMode:
5316       case Training:
5317         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5318             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5319             /* User is moving for Black */
5320             if (WhiteOnMove(currentMove)) {
5321                 DisplayMoveError(_("It is White's turn"));
5322                 return ImpossibleMove;
5323             }
5324         } else {
5325             /* User is moving for White */
5326             if (!WhiteOnMove(currentMove)) {
5327                 DisplayMoveError(_("It is Black's turn"));
5328                 return ImpossibleMove;
5329             }
5330         }
5331         break;
5332
5333       case IcsPlayingBlack:
5334         /* User is moving for Black */
5335         if (WhiteOnMove(currentMove)) {
5336             if (!appData.premove) {
5337                 DisplayMoveError(_("It is White's turn"));
5338             } else if (toX >= 0 && toY >= 0) {
5339                 premoveToX = toX;
5340                 premoveToY = toY;
5341                 premoveFromX = fromX;
5342                 premoveFromY = fromY;
5343                 premovePromoChar = promoChar;
5344                 gotPremove = 1;
5345                 if (appData.debugMode) 
5346                     fprintf(debugFP, "Got premove: fromX %d,"
5347                             "fromY %d, toX %d, toY %d\n",
5348                             fromX, fromY, toX, toY);
5349             }
5350             return ImpossibleMove;
5351         }
5352         break;
5353
5354       case IcsPlayingWhite:
5355         /* User is moving for White */
5356         if (!WhiteOnMove(currentMove)) {
5357             if (!appData.premove) {
5358                 DisplayMoveError(_("It is Black's turn"));
5359             } else if (toX >= 0 && toY >= 0) {
5360                 premoveToX = toX;
5361                 premoveToY = toY;
5362                 premoveFromX = fromX;
5363                 premoveFromY = fromY;
5364                 premovePromoChar = promoChar;
5365                 gotPremove = 1;
5366                 if (appData.debugMode) 
5367                     fprintf(debugFP, "Got premove: fromX %d,"
5368                             "fromY %d, toX %d, toY %d\n",
5369                             fromX, fromY, toX, toY);
5370             }
5371             return ImpossibleMove;
5372         }
5373         break;
5374
5375       default:
5376         break;
5377
5378       case EditPosition:
5379         /* EditPosition, empty square, or different color piece;
5380            click-click move is possible */
5381         if (toX == -2 || toY == -2) {
5382             boards[0][fromY][fromX] = EmptySquare;
5383             return AmbiguousMove;
5384         } else if (toX >= 0 && toY >= 0) {
5385             boards[0][toY][toX] = boards[0][fromY][fromX];
5386             boards[0][fromY][fromX] = EmptySquare;
5387             return AmbiguousMove;
5388         }
5389         return ImpossibleMove;
5390     }
5391
5392     if(toX < 0 || toY < 0) return ImpossibleMove;
5393     pdown = boards[currentMove][fromY][fromX];
5394     pup = boards[currentMove][toY][toX];
5395
5396     /* [HGM] If move started in holdings, it means a drop */
5397     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5398          if( pup != EmptySquare ) return ImpossibleMove;
5399          if(appData.testLegality) {
5400              /* it would be more logical if LegalityTest() also figured out
5401               * which drops are legal. For now we forbid pawns on back rank.
5402               * Shogi is on its own here...
5403               */
5404              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5405                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5406                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5407          }
5408          return WhiteDrop; /* Not needed to specify white or black yet */
5409     }
5410
5411     userOfferedDraw = FALSE;
5412         
5413     /* [HGM] always test for legality, to get promotion info */
5414     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5415                                          fromY, fromX, toY, toX, promoChar);
5416     /* [HGM] but possibly ignore an IllegalMove result */
5417     if (appData.testLegality) {
5418         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5419             DisplayMoveError(_("Illegal move"));
5420             return ImpossibleMove;
5421         }
5422     }
5423
5424     return moveType;
5425     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5426        function is made into one that returns an OK move type if FinishMove
5427        should be called. This to give the calling driver routine the
5428        opportunity to finish the userMove input with a promotion popup,
5429        without bothering the user with this for invalid or illegal moves */
5430
5431 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5432 }
5433
5434 /* Common tail of UserMoveEvent and DropMenuEvent */
5435 int
5436 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5437      ChessMove moveType;
5438      int fromX, fromY, toX, toY;
5439      /*char*/int promoChar;
5440 {
5441     char *bookHit = 0;
5442
5443     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5444         // [HGM] superchess: suppress promotions to non-available piece
5445         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5446         if(WhiteOnMove(currentMove)) {
5447             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5448         } else {
5449             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5450         }
5451     }
5452
5453     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5454        move type in caller when we know the move is a legal promotion */
5455     if(moveType == NormalMove && promoChar)
5456         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5457
5458     /* [HGM] convert drag-and-drop piece drops to standard form */
5459     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5460          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5461            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5462                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5463            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5464            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5465            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5466            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5467          fromY = DROP_RANK;
5468     }
5469
5470     /* [HGM] <popupFix> The following if has been moved here from
5471        UserMoveEvent(). Because it seemed to belong here (why not allow
5472        piece drops in training games?), and because it can only be
5473        performed after it is known to what we promote. */
5474     if (gameMode == Training) {
5475       /* compare the move played on the board to the next move in the
5476        * game. If they match, display the move and the opponent's response. 
5477        * If they don't match, display an error message.
5478        */
5479       int saveAnimate;
5480       Board testBoard;
5481       CopyBoard(testBoard, boards[currentMove]);
5482       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5483
5484       if (CompareBoards(testBoard, boards[currentMove+1])) {
5485         ForwardInner(currentMove+1);
5486
5487         /* Autoplay the opponent's response.
5488          * if appData.animate was TRUE when Training mode was entered,
5489          * the response will be animated.
5490          */
5491         saveAnimate = appData.animate;
5492         appData.animate = animateTraining;
5493         ForwardInner(currentMove+1);
5494         appData.animate = saveAnimate;
5495
5496         /* check for the end of the game */
5497         if (currentMove >= forwardMostMove) {
5498           gameMode = PlayFromGameFile;
5499           ModeHighlight();
5500           SetTrainingModeOff();
5501           DisplayInformation(_("End of game"));
5502         }
5503       } else {
5504         DisplayError(_("Incorrect move"), 0);
5505       }
5506       return 1;
5507     }
5508
5509   /* Ok, now we know that the move is good, so we can kill
5510      the previous line in Analysis Mode */
5511   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5512                                 && currentMove < forwardMostMove) {
5513     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5514   }
5515
5516   /* If we need the chess program but it's dead, restart it */
5517   ResurrectChessProgram();
5518
5519   /* A user move restarts a paused game*/
5520   if (pausing)
5521     PauseEvent();
5522
5523   thinkOutput[0] = NULLCHAR;
5524
5525   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5526
5527   if (gameMode == BeginningOfGame) {
5528     if (appData.noChessProgram) {
5529       gameMode = EditGame;
5530       SetGameInfo();
5531     } else {
5532       char buf[MSG_SIZ];
5533       gameMode = MachinePlaysBlack;
5534       StartClocks();
5535       SetGameInfo();
5536       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5537       DisplayTitle(buf);
5538       if (first.sendName) {
5539         sprintf(buf, "name %s\n", gameInfo.white);
5540         SendToProgram(buf, &first);
5541       }
5542       StartClocks();
5543     }
5544     ModeHighlight();
5545   }
5546
5547   /* Relay move to ICS or chess engine */
5548   if (appData.icsActive) {
5549     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5550         gameMode == IcsExamining) {
5551       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5552       ics_user_moved = 1;
5553     }
5554   } else {
5555     if (first.sendTime && (gameMode == BeginningOfGame ||
5556                            gameMode == MachinePlaysWhite ||
5557                            gameMode == MachinePlaysBlack)) {
5558       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5559     }
5560     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5561          // [HGM] book: if program might be playing, let it use book
5562         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5563         first.maybeThinking = TRUE;
5564     } else SendMoveToProgram(forwardMostMove-1, &first);
5565     if (currentMove == cmailOldMove + 1) {
5566       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5567     }
5568   }
5569
5570   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5571
5572   switch (gameMode) {
5573   case EditGame:
5574     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5575     case MT_NONE:
5576     case MT_CHECK:
5577       break;
5578     case MT_CHECKMATE:
5579     case MT_STAINMATE:
5580       if (WhiteOnMove(currentMove)) {
5581         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5582       } else {
5583         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5584       }
5585       break;
5586     case MT_STALEMATE:
5587       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5588       break;
5589     }
5590     break;
5591     
5592   case MachinePlaysBlack:
5593   case MachinePlaysWhite:
5594     /* disable certain menu options while machine is thinking */
5595     SetMachineThinkingEnables();
5596     break;
5597
5598   default:
5599     break;
5600   }
5601
5602   if(bookHit) { // [HGM] book: simulate book reply
5603         static char bookMove[MSG_SIZ]; // a bit generous?
5604
5605         programStats.nodes = programStats.depth = programStats.time = 
5606         programStats.score = programStats.got_only_move = 0;
5607         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5608
5609         strcpy(bookMove, "move ");
5610         strcat(bookMove, bookHit);
5611         HandleMachineMove(bookMove, &first);
5612   }
5613   return 1;
5614 }
5615
5616 void
5617 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5618      int fromX, fromY, toX, toY;
5619      int promoChar;
5620 {
5621     /* [HGM] This routine was added to allow calling of its two logical
5622        parts from other modules in the old way. Before, UserMoveEvent()
5623        automatically called FinishMove() if the move was OK, and returned
5624        otherwise. I separated the two, in order to make it possible to
5625        slip a promotion popup in between. But that it always needs two
5626        calls, to the first part, (now called UserMoveTest() ), and to
5627        FinishMove if the first part succeeded. Calls that do not need
5628        to do anything in between, can call this routine the old way. 
5629     */
5630     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5631 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5632     if(moveType == AmbiguousMove)
5633         DrawPosition(FALSE, boards[currentMove]);
5634     else if(moveType != ImpossibleMove && moveType != Comment)
5635         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5636 }
5637
5638 void
5639 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5640      Board board;
5641      int flags;
5642      ChessMove kind;
5643      int rf, ff, rt, ft;
5644      VOIDSTAR closure;
5645 {
5646     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5647     Markers *m = (Markers *) closure;
5648     if(rf == fromY && ff == fromX)
5649         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5650                          || kind == WhiteCapturesEnPassant
5651                          || kind == BlackCapturesEnPassant);
5652     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5653 }
5654
5655 void
5656 MarkTargetSquares(int clear)
5657 {
5658   int x, y;
5659   if(!appData.markers || !appData.highlightDragging || 
5660      !appData.testLegality || gameMode == EditPosition) return;
5661   if(clear) {
5662     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5663   } else {
5664     int capt = 0;
5665     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5666     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5667       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5668       if(capt)
5669       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5670     }
5671   }
5672   DrawPosition(TRUE, NULL);
5673 }
5674
5675 void LeftClick(ClickType clickType, int xPix, int yPix)
5676 {
5677     int x, y;
5678     Boolean saveAnimate;
5679     static int second = 0, promotionChoice = 0;
5680     char promoChoice = NULLCHAR;
5681
5682     if (clickType == Press) ErrorPopDown();
5683     MarkTargetSquares(1);
5684
5685     x = EventToSquare(xPix, BOARD_WIDTH);
5686     y = EventToSquare(yPix, BOARD_HEIGHT);
5687     if (!flipView && y >= 0) {
5688         y = BOARD_HEIGHT - 1 - y;
5689     }
5690     if (flipView && x >= 0) {
5691         x = BOARD_WIDTH - 1 - x;
5692     }
5693
5694     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5695         if(clickType == Release) return; // ignore upclick of click-click destination
5696         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5697         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5698         if(gameInfo.holdingsWidth && 
5699                 (WhiteOnMove(currentMove) 
5700                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5701                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5702             // click in right holdings, for determining promotion piece
5703             ChessSquare p = boards[currentMove][y][x];
5704             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5705             if(p != EmptySquare) {
5706                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5707                 fromX = fromY = -1;
5708                 return;
5709             }
5710         }
5711         DrawPosition(FALSE, boards[currentMove]);
5712         return;
5713     }
5714
5715     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5716     if(clickType == Press
5717             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5718               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5719               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5720         return;
5721
5722     if (fromX == -1) {
5723         if (clickType == Press) {
5724             /* First square */
5725             if (OKToStartUserMove(x, y)) {
5726                 fromX = x;
5727                 fromY = y;
5728                 second = 0;
5729                 MarkTargetSquares(0);
5730                 DragPieceBegin(xPix, yPix);
5731                 if (appData.highlightDragging) {
5732                     SetHighlights(x, y, -1, -1);
5733                 }
5734             }
5735         }
5736         return;
5737     }
5738
5739     /* fromX != -1 */
5740     if (clickType == Press && gameMode != EditPosition) {
5741         ChessSquare fromP;
5742         ChessSquare toP;
5743         int frc;
5744
5745         // ignore off-board to clicks
5746         if(y < 0 || x < 0) return;
5747
5748         /* Check if clicking again on the same color piece */
5749         fromP = boards[currentMove][fromY][fromX];
5750         toP = boards[currentMove][y][x];
5751         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5752         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5753              WhitePawn <= toP && toP <= WhiteKing &&
5754              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5755              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5756             (BlackPawn <= fromP && fromP <= BlackKing && 
5757              BlackPawn <= toP && toP <= BlackKing &&
5758              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5759              !(fromP == BlackKing && toP == BlackRook && frc))) {
5760             /* Clicked again on same color piece -- changed his mind */
5761             second = (x == fromX && y == fromY);
5762             if (appData.highlightDragging) {
5763                 SetHighlights(x, y, -1, -1);
5764             } else {
5765                 ClearHighlights();
5766             }
5767             if (OKToStartUserMove(x, y)) {
5768                 fromX = x;
5769                 fromY = y;
5770                 MarkTargetSquares(0);
5771                 DragPieceBegin(xPix, yPix);
5772             }
5773             return;
5774         }
5775         // ignore clicks on holdings
5776         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5777     }
5778
5779     if (clickType == Release && x == fromX && y == fromY) {
5780         DragPieceEnd(xPix, yPix);
5781         if (appData.animateDragging) {
5782             /* Undo animation damage if any */
5783             DrawPosition(FALSE, NULL);
5784         }
5785         if (second) {
5786             /* Second up/down in same square; just abort move */
5787             second = 0;
5788             fromX = fromY = -1;
5789             ClearHighlights();
5790             gotPremove = 0;
5791             ClearPremoveHighlights();
5792         } else {
5793             /* First upclick in same square; start click-click mode */
5794             SetHighlights(x, y, -1, -1);
5795         }
5796         return;
5797     }
5798
5799     /* we now have a different from- and (possibly off-board) to-square */
5800     /* Completed move */
5801     toX = x;
5802     toY = y;
5803     saveAnimate = appData.animate;
5804     if (clickType == Press) {
5805         /* Finish clickclick move */
5806         if (appData.animate || appData.highlightLastMove) {
5807             SetHighlights(fromX, fromY, toX, toY);
5808         } else {
5809             ClearHighlights();
5810         }
5811     } else {
5812         /* Finish drag move */
5813         if (appData.highlightLastMove) {
5814             SetHighlights(fromX, fromY, toX, toY);
5815         } else {
5816             ClearHighlights();
5817         }
5818         DragPieceEnd(xPix, yPix);
5819         /* Don't animate move and drag both */
5820         appData.animate = FALSE;
5821     }
5822
5823     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5824     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5825         ClearHighlights();
5826         fromX = fromY = -1;
5827         DrawPosition(TRUE, NULL);
5828         return;
5829     }
5830
5831     // off-board moves should not be highlighted
5832     if(x < 0 || x < 0) ClearHighlights();
5833
5834     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5835         SetHighlights(fromX, fromY, toX, toY);
5836         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5837             // [HGM] super: promotion to captured piece selected from holdings
5838             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5839             promotionChoice = TRUE;
5840             // kludge follows to temporarily execute move on display, without promoting yet
5841             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5842             boards[currentMove][toY][toX] = p;
5843             DrawPosition(FALSE, boards[currentMove]);
5844             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5845             boards[currentMove][toY][toX] = q;
5846             DisplayMessage("Click in holdings to choose piece", "");
5847             return;
5848         }
5849         PromotionPopUp();
5850     } else {
5851         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5852         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5853         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5854         fromX = fromY = -1;
5855     }
5856     appData.animate = saveAnimate;
5857     if (appData.animate || appData.animateDragging) {
5858         /* Undo animation damage if needed */
5859         DrawPosition(FALSE, NULL);
5860     }
5861 }
5862
5863 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5864 {
5865 //    char * hint = lastHint;
5866     FrontEndProgramStats stats;
5867
5868     stats.which = cps == &first ? 0 : 1;
5869     stats.depth = cpstats->depth;
5870     stats.nodes = cpstats->nodes;
5871     stats.score = cpstats->score;
5872     stats.time = cpstats->time;
5873     stats.pv = cpstats->movelist;
5874     stats.hint = lastHint;
5875     stats.an_move_index = 0;
5876     stats.an_move_count = 0;
5877
5878     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5879         stats.hint = cpstats->move_name;
5880         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5881         stats.an_move_count = cpstats->nr_moves;
5882     }
5883
5884     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5885
5886     SetProgramStats( &stats );
5887 }
5888
5889 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5890 {   // [HGM] book: this routine intercepts moves to simulate book replies
5891     char *bookHit = NULL;
5892
5893     //first determine if the incoming move brings opponent into his book
5894     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5895         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5896     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5897     if(bookHit != NULL && !cps->bookSuspend) {
5898         // make sure opponent is not going to reply after receiving move to book position
5899         SendToProgram("force\n", cps);
5900         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5901     }
5902     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5903     // now arrange restart after book miss
5904     if(bookHit) {
5905         // after a book hit we never send 'go', and the code after the call to this routine
5906         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5907         char buf[MSG_SIZ];
5908         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5909         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5910         SendToProgram(buf, cps);
5911         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5912     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5913         SendToProgram("go\n", cps);
5914         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5915     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5916         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5917             SendToProgram("go\n", cps); 
5918         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5919     }
5920     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5921 }
5922
5923 char *savedMessage;
5924 ChessProgramState *savedState;
5925 void DeferredBookMove(void)
5926 {
5927         if(savedState->lastPing != savedState->lastPong)
5928                     ScheduleDelayedEvent(DeferredBookMove, 10);
5929         else
5930         HandleMachineMove(savedMessage, savedState);
5931 }
5932
5933 void
5934 HandleMachineMove(message, cps)
5935      char *message;
5936      ChessProgramState *cps;
5937 {
5938     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5939     char realname[MSG_SIZ];
5940     int fromX, fromY, toX, toY;
5941     ChessMove moveType;
5942     char promoChar;
5943     char *p;
5944     int machineWhite;
5945     char *bookHit;
5946
5947     cps->userError = 0;
5948
5949 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5950     /*
5951      * Kludge to ignore BEL characters
5952      */
5953     while (*message == '\007') message++;
5954
5955     /*
5956      * [HGM] engine debug message: ignore lines starting with '#' character
5957      */
5958     if(cps->debug && *message == '#') return;
5959
5960     /*
5961      * Look for book output
5962      */
5963     if (cps == &first && bookRequested) {
5964         if (message[0] == '\t' || message[0] == ' ') {
5965             /* Part of the book output is here; append it */
5966             strcat(bookOutput, message);
5967             strcat(bookOutput, "  \n");
5968             return;
5969         } else if (bookOutput[0] != NULLCHAR) {
5970             /* All of book output has arrived; display it */
5971             char *p = bookOutput;
5972             while (*p != NULLCHAR) {
5973                 if (*p == '\t') *p = ' ';
5974                 p++;
5975             }
5976             DisplayInformation(bookOutput);
5977             bookRequested = FALSE;
5978             /* Fall through to parse the current output */
5979         }
5980     }
5981
5982     /*
5983      * Look for machine move.
5984      */
5985     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5986         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5987     {
5988         /* This method is only useful on engines that support ping */
5989         if (cps->lastPing != cps->lastPong) {
5990           if (gameMode == BeginningOfGame) {
5991             /* Extra move from before last new; ignore */
5992             if (appData.debugMode) {
5993                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5994             }
5995           } else {
5996             if (appData.debugMode) {
5997                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5998                         cps->which, gameMode);
5999             }
6000
6001             SendToProgram("undo\n", cps);
6002           }
6003           return;
6004         }
6005
6006         switch (gameMode) {
6007           case BeginningOfGame:
6008             /* Extra move from before last reset; ignore */
6009             if (appData.debugMode) {
6010                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6011             }
6012             return;
6013
6014           case EndOfGame:
6015           case IcsIdle:
6016           default:
6017             /* Extra move after we tried to stop.  The mode test is
6018                not a reliable way of detecting this problem, but it's
6019                the best we can do on engines that don't support ping.
6020             */
6021             if (appData.debugMode) {
6022                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6023                         cps->which, gameMode);
6024             }
6025             SendToProgram("undo\n", cps);
6026             return;
6027
6028           case MachinePlaysWhite:
6029           case IcsPlayingWhite:
6030             machineWhite = TRUE;
6031             break;
6032
6033           case MachinePlaysBlack:
6034           case IcsPlayingBlack:
6035             machineWhite = FALSE;
6036             break;
6037
6038           case TwoMachinesPlay:
6039             machineWhite = (cps->twoMachinesColor[0] == 'w');
6040             break;
6041         }
6042         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6043             if (appData.debugMode) {
6044                 fprintf(debugFP,
6045                         "Ignoring move out of turn by %s, gameMode %d"
6046                         ", forwardMost %d\n",
6047                         cps->which, gameMode, forwardMostMove);
6048             }
6049             return;
6050         }
6051
6052     if (appData.debugMode) { int f = forwardMostMove;
6053         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6054                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6055                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6056     }
6057         if(cps->alphaRank) AlphaRank(machineMove, 4);
6058         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6059                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6060             /* Machine move could not be parsed; ignore it. */
6061             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6062                     machineMove, cps->which);
6063             DisplayError(buf1, 0);
6064             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6065                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6066             if (gameMode == TwoMachinesPlay) {
6067               GameEnds(machineWhite ? BlackWins : WhiteWins,
6068                        buf1, GE_XBOARD);
6069             }
6070             return;
6071         }
6072
6073         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6074         /* So we have to redo legality test with true e.p. status here,  */
6075         /* to make sure an illegal e.p. capture does not slip through,   */
6076         /* to cause a forfeit on a justified illegal-move complaint      */
6077         /* of the opponent.                                              */
6078         if( gameMode==TwoMachinesPlay && appData.testLegality
6079             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6080                                                               ) {
6081            ChessMove moveType;
6082            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6083                              fromY, fromX, toY, toX, promoChar);
6084             if (appData.debugMode) {
6085                 int i;
6086                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6087                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6088                 fprintf(debugFP, "castling rights\n");
6089             }
6090             if(moveType == IllegalMove) {
6091                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6092                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6093                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6094                            buf1, GE_XBOARD);
6095                 return;
6096            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6097            /* [HGM] Kludge to handle engines that send FRC-style castling
6098               when they shouldn't (like TSCP-Gothic) */
6099            switch(moveType) {
6100              case WhiteASideCastleFR:
6101              case BlackASideCastleFR:
6102                toX+=2;
6103                currentMoveString[2]++;
6104                break;
6105              case WhiteHSideCastleFR:
6106              case BlackHSideCastleFR:
6107                toX--;
6108                currentMoveString[2]--;
6109                break;
6110              default: ; // nothing to do, but suppresses warning of pedantic compilers
6111            }
6112         }
6113         hintRequested = FALSE;
6114         lastHint[0] = NULLCHAR;
6115         bookRequested = FALSE;
6116         /* Program may be pondering now */
6117         cps->maybeThinking = TRUE;
6118         if (cps->sendTime == 2) cps->sendTime = 1;
6119         if (cps->offeredDraw) cps->offeredDraw--;
6120
6121 #if ZIPPY
6122         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6123             first.initDone) {
6124           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6125           ics_user_moved = 1;
6126           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6127                 char buf[3*MSG_SIZ];
6128
6129                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6130                         programStats.score / 100.,
6131                         programStats.depth,
6132                         programStats.time / 100.,
6133                         (unsigned int)programStats.nodes,
6134                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6135                         programStats.movelist);
6136                 SendToICS(buf);
6137 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6138           }
6139         }
6140 #endif
6141         /* currentMoveString is set as a side-effect of ParseOneMove */
6142         strcpy(machineMove, currentMoveString);
6143         strcat(machineMove, "\n");
6144         strcpy(moveList[forwardMostMove], machineMove);
6145
6146         /* [AS] Save move info and clear stats for next move */
6147         pvInfoList[ forwardMostMove ].score = programStats.score;
6148         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6149         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6150         ClearProgramStats();
6151         thinkOutput[0] = NULLCHAR;
6152         hiddenThinkOutputState = 0;
6153
6154         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6155
6156         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6157         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6158             int count = 0;
6159
6160             while( count < adjudicateLossPlies ) {
6161                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6162
6163                 if( count & 1 ) {
6164                     score = -score; /* Flip score for winning side */
6165                 }
6166
6167                 if( score > adjudicateLossThreshold ) {
6168                     break;
6169                 }
6170
6171                 count++;
6172             }
6173
6174             if( count >= adjudicateLossPlies ) {
6175                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6176
6177                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6178                     "Xboard adjudication", 
6179                     GE_XBOARD );
6180
6181                 return;
6182             }
6183         }
6184
6185         if( gameMode == TwoMachinesPlay ) {
6186           // [HGM] some adjudications useful with buggy engines
6187             int k, count = 0; static int bare = 1;
6188           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6189
6190
6191             if( appData.testLegality )
6192             {   /* [HGM] Some more adjudications for obstinate engines */
6193                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6194                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6195                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6196                 static int moveCount = 6;
6197                 ChessMove result;
6198                 char *reason = NULL;
6199
6200                 /* Count what is on board. */
6201                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6202                 {   ChessSquare p = boards[forwardMostMove][i][j];
6203                     int m=i;
6204
6205                     switch((int) p)
6206                     {   /* count B,N,R and other of each side */
6207                         case WhiteKing:
6208                         case BlackKing:
6209                              NrK++; break; // [HGM] atomic: count Kings
6210                         case WhiteKnight:
6211                              NrWN++; break;
6212                         case WhiteBishop:
6213                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6214                              bishopsColor |= 1 << ((i^j)&1);
6215                              NrWB++; break;
6216                         case BlackKnight:
6217                              NrBN++; break;
6218                         case BlackBishop:
6219                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6220                              bishopsColor |= 1 << ((i^j)&1);
6221                              NrBB++; break;
6222                         case WhiteRook:
6223                              NrWR++; break;
6224                         case BlackRook:
6225                              NrBR++; break;
6226                         case WhiteQueen:
6227                              NrWQ++; break;
6228                         case BlackQueen:
6229                              NrBQ++; break;
6230                         case EmptySquare: 
6231                              break;
6232                         case BlackPawn:
6233                              m = 7-i;
6234                         case WhitePawn:
6235                              PawnAdvance += m; NrPawns++;
6236                     }
6237                     NrPieces += (p != EmptySquare);
6238                     NrW += ((int)p < (int)BlackPawn);
6239                     if(gameInfo.variant == VariantXiangqi && 
6240                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6241                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6242                         NrW -= ((int)p < (int)BlackPawn);
6243                     }
6244                 }
6245
6246                 /* Some material-based adjudications that have to be made before stalemate test */
6247                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6248                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6249                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6250                      if(appData.checkMates) {
6251                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6252                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6253                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6254                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6255                          return;
6256                      }
6257                 }
6258
6259                 /* Bare King in Shatranj (loses) or Losers (wins) */
6260                 if( NrW == 1 || NrPieces - NrW == 1) {
6261                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6262                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6263                      if(appData.checkMates) {
6264                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6265                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6266                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6267                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6268                          return;
6269                      }
6270                   } else
6271                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6272                   {    /* bare King */
6273                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6274                         if(appData.checkMates) {
6275                             /* but only adjudicate if adjudication enabled */
6276                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6277                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6278                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6279                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6280                             return;
6281                         }
6282                   }
6283                 } else bare = 1;
6284
6285
6286             // don't wait for engine to announce game end if we can judge ourselves
6287             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6288               case MT_CHECK:
6289                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6290                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6291                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6292                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6293                             checkCnt++;
6294                         if(checkCnt >= 2) {
6295                             reason = "Xboard adjudication: 3rd check";
6296                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6297                             break;
6298                         }
6299                     }
6300                 }
6301               case MT_NONE:
6302               default:
6303                 break;
6304               case MT_STALEMATE:
6305               case MT_STAINMATE:
6306                 reason = "Xboard adjudication: Stalemate";
6307                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6308                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6309                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6310                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6311                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6312                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6313                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6314                                                                         EP_CHECKMATE : EP_WINS);
6315                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6316                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6317                 }
6318                 break;
6319               case MT_CHECKMATE:
6320                 reason = "Xboard adjudication: Checkmate";
6321                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6322                 break;
6323             }
6324
6325                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6326                     case EP_STALEMATE:
6327                         result = GameIsDrawn; break;
6328                     case EP_CHECKMATE:
6329                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6330                     case EP_WINS:
6331                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6332                     default:
6333                         result = (ChessMove) 0;
6334                 }
6335                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6336                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6337                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6338                     GameEnds( result, reason, GE_XBOARD );
6339                     return;
6340                 }
6341
6342                 /* Next absolutely insufficient mating material. */
6343                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6344                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6345                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6346                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6347                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6348
6349                      /* always flag draws, for judging claims */
6350                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6351
6352                      if(appData.materialDraws) {
6353                          /* but only adjudicate them if adjudication enabled */
6354                          SendToProgram("force\n", cps->other); // suppress reply
6355                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6356                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6357                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6358                          return;
6359                      }
6360                 }
6361
6362                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6363                 if(NrPieces == 4 && 
6364                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6365                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6366                    || NrWN==2 || NrBN==2     /* KNNK */
6367                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6368                   ) ) {
6369                      if(--moveCount < 0 && appData.trivialDraws)
6370                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6371                           SendToProgram("force\n", cps->other); // suppress reply
6372                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6373                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6374                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6375                           return;
6376                      }
6377                 } else moveCount = 6;
6378             }
6379           }
6380           
6381           if (appData.debugMode) { int i;
6382             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6383                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6384                     appData.drawRepeats);
6385             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6386               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6387             
6388           }
6389
6390                 /* Check for rep-draws */
6391                 count = 0;
6392                 for(k = forwardMostMove-2;
6393                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6394                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6395                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6396                     k-=2)
6397                 {   int rights=0;
6398                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6399                         /* compare castling rights */
6400                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6401                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6402                                 rights++; /* King lost rights, while rook still had them */
6403                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6404                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6405                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6406                                    rights++; /* but at least one rook lost them */
6407                         }
6408                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6409                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6410                                 rights++; 
6411                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6412                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6413                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6414                                    rights++;
6415                         }
6416                         if( rights == 0 && ++count > appData.drawRepeats-2
6417                             && appData.drawRepeats > 1) {
6418                              /* adjudicate after user-specified nr of repeats */
6419                              SendToProgram("force\n", cps->other); // suppress reply
6420                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6421                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6422                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6423                                 // [HGM] xiangqi: check for forbidden perpetuals
6424                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6425                                 for(m=forwardMostMove; m>k; m-=2) {
6426                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6427                                         ourPerpetual = 0; // the current mover did not always check
6428                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6429                                         hisPerpetual = 0; // the opponent did not always check
6430                                 }
6431                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6432                                                                         ourPerpetual, hisPerpetual);
6433                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6434                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6435                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6436                                     return;
6437                                 }
6438                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6439                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6440                                 // Now check for perpetual chases
6441                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6442                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6443                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6444                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6445                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6446                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6447                                         return;
6448                                     }
6449                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6450                                         break; // Abort repetition-checking loop.
6451                                 }
6452                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6453                              }
6454                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6455                              return;
6456                         }
6457                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6458                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6459                     }
6460                 }
6461
6462                 /* Now we test for 50-move draws. Determine ply count */
6463                 count = forwardMostMove;
6464                 /* look for last irreversble move */
6465                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6466                     count--;
6467                 /* if we hit starting position, add initial plies */
6468                 if( count == backwardMostMove )
6469                     count -= initialRulePlies;
6470                 count = forwardMostMove - count; 
6471                 if( count >= 100)
6472                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6473                          /* this is used to judge if draw claims are legal */
6474                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6475                          SendToProgram("force\n", cps->other); // suppress reply
6476                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6477                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6478                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6479                          return;
6480                 }
6481
6482                 /* if draw offer is pending, treat it as a draw claim
6483                  * when draw condition present, to allow engines a way to
6484                  * claim draws before making their move to avoid a race
6485                  * condition occurring after their move
6486                  */
6487                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6488                          char *p = NULL;
6489                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6490                              p = "Draw claim: 50-move rule";
6491                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6492                              p = "Draw claim: 3-fold repetition";
6493                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6494                              p = "Draw claim: insufficient mating material";
6495                          if( p != NULL ) {
6496                              SendToProgram("force\n", cps->other); // suppress reply
6497                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6498                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6499                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6500                              return;
6501                          }
6502                 }
6503
6504
6505                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6506                     SendToProgram("force\n", cps->other); // suppress reply
6507                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6508                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6509
6510                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6511
6512                     return;
6513                 }
6514         }
6515
6516         bookHit = NULL;
6517         if (gameMode == TwoMachinesPlay) {
6518             /* [HGM] relaying draw offers moved to after reception of move */
6519             /* and interpreting offer as claim if it brings draw condition */
6520             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6521                 SendToProgram("draw\n", cps->other);
6522             }
6523             if (cps->other->sendTime) {
6524                 SendTimeRemaining(cps->other,
6525                                   cps->other->twoMachinesColor[0] == 'w');
6526             }
6527             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6528             if (firstMove && !bookHit) {
6529                 firstMove = FALSE;
6530                 if (cps->other->useColors) {
6531                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6532                 }
6533                 SendToProgram("go\n", cps->other);
6534             }
6535             cps->other->maybeThinking = TRUE;
6536         }
6537
6538         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6539         
6540         if (!pausing && appData.ringBellAfterMoves) {
6541             RingBell();
6542         }
6543
6544         /* 
6545          * Reenable menu items that were disabled while
6546          * machine was thinking
6547          */
6548         if (gameMode != TwoMachinesPlay)
6549             SetUserThinkingEnables();
6550
6551         // [HGM] book: after book hit opponent has received move and is now in force mode
6552         // force the book reply into it, and then fake that it outputted this move by jumping
6553         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6554         if(bookHit) {
6555                 static char bookMove[MSG_SIZ]; // a bit generous?
6556
6557                 strcpy(bookMove, "move ");
6558                 strcat(bookMove, bookHit);
6559                 message = bookMove;
6560                 cps = cps->other;
6561                 programStats.nodes = programStats.depth = programStats.time = 
6562                 programStats.score = programStats.got_only_move = 0;
6563                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6564
6565                 if(cps->lastPing != cps->lastPong) {
6566                     savedMessage = message; // args for deferred call
6567                     savedState = cps;
6568                     ScheduleDelayedEvent(DeferredBookMove, 10);
6569                     return;
6570                 }
6571                 goto FakeBookMove;
6572         }
6573
6574         return;
6575     }
6576
6577     /* Set special modes for chess engines.  Later something general
6578      *  could be added here; for now there is just one kludge feature,
6579      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6580      *  when "xboard" is given as an interactive command.
6581      */
6582     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6583         cps->useSigint = FALSE;
6584         cps->useSigterm = FALSE;
6585     }
6586     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6587       ParseFeatures(message+8, cps);
6588       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6589     }
6590
6591     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6592      * want this, I was asked to put it in, and obliged.
6593      */
6594     if (!strncmp(message, "setboard ", 9)) {
6595         Board initial_position;
6596
6597         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6598
6599         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6600             DisplayError(_("Bad FEN received from engine"), 0);
6601             return ;
6602         } else {
6603            Reset(TRUE, FALSE);
6604            CopyBoard(boards[0], initial_position);
6605            initialRulePlies = FENrulePlies;
6606            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6607            else gameMode = MachinePlaysBlack;                 
6608            DrawPosition(FALSE, boards[currentMove]);
6609         }
6610         return;
6611     }
6612
6613     /*
6614      * Look for communication commands
6615      */
6616     if (!strncmp(message, "telluser ", 9)) {
6617         DisplayNote(message + 9);
6618         return;
6619     }
6620     if (!strncmp(message, "tellusererror ", 14)) {
6621         cps->userError = 1;
6622         DisplayError(message + 14, 0);
6623         return;
6624     }
6625     if (!strncmp(message, "tellopponent ", 13)) {
6626       if (appData.icsActive) {
6627         if (loggedOn) {
6628           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6629           SendToICS(buf1);
6630         }
6631       } else {
6632         DisplayNote(message + 13);
6633       }
6634       return;
6635     }
6636     if (!strncmp(message, "tellothers ", 11)) {
6637       if (appData.icsActive) {
6638         if (loggedOn) {
6639           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6640           SendToICS(buf1);
6641         }
6642       }
6643       return;
6644     }
6645     if (!strncmp(message, "tellall ", 8)) {
6646       if (appData.icsActive) {
6647         if (loggedOn) {
6648           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6649           SendToICS(buf1);
6650         }
6651       } else {
6652         DisplayNote(message + 8);
6653       }
6654       return;
6655     }
6656     if (strncmp(message, "warning", 7) == 0) {
6657         /* Undocumented feature, use tellusererror in new code */
6658         DisplayError(message, 0);
6659         return;
6660     }
6661     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6662         strcpy(realname, cps->tidy);
6663         strcat(realname, " query");
6664         AskQuestion(realname, buf2, buf1, cps->pr);
6665         return;
6666     }
6667     /* Commands from the engine directly to ICS.  We don't allow these to be 
6668      *  sent until we are logged on. Crafty kibitzes have been known to 
6669      *  interfere with the login process.
6670      */
6671     if (loggedOn) {
6672         if (!strncmp(message, "tellics ", 8)) {
6673             SendToICS(message + 8);
6674             SendToICS("\n");
6675             return;
6676         }
6677         if (!strncmp(message, "tellicsnoalias ", 15)) {
6678             SendToICS(ics_prefix);
6679             SendToICS(message + 15);
6680             SendToICS("\n");
6681             return;
6682         }
6683         /* The following are for backward compatibility only */
6684         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6685             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6686             SendToICS(ics_prefix);
6687             SendToICS(message);
6688             SendToICS("\n");
6689             return;
6690         }
6691     }
6692     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6693         return;
6694     }
6695     /*
6696      * If the move is illegal, cancel it and redraw the board.
6697      * Also deal with other error cases.  Matching is rather loose
6698      * here to accommodate engines written before the spec.
6699      */
6700     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6701         strncmp(message, "Error", 5) == 0) {
6702         if (StrStr(message, "name") || 
6703             StrStr(message, "rating") || StrStr(message, "?") ||
6704             StrStr(message, "result") || StrStr(message, "board") ||
6705             StrStr(message, "bk") || StrStr(message, "computer") ||
6706             StrStr(message, "variant") || StrStr(message, "hint") ||
6707             StrStr(message, "random") || StrStr(message, "depth") ||
6708             StrStr(message, "accepted")) {
6709             return;
6710         }
6711         if (StrStr(message, "protover")) {
6712           /* Program is responding to input, so it's apparently done
6713              initializing, and this error message indicates it is
6714              protocol version 1.  So we don't need to wait any longer
6715              for it to initialize and send feature commands. */
6716           FeatureDone(cps, 1);
6717           cps->protocolVersion = 1;
6718           return;
6719         }
6720         cps->maybeThinking = FALSE;
6721
6722         if (StrStr(message, "draw")) {
6723             /* Program doesn't have "draw" command */
6724             cps->sendDrawOffers = 0;
6725             return;
6726         }
6727         if (cps->sendTime != 1 &&
6728             (StrStr(message, "time") || StrStr(message, "otim"))) {
6729           /* Program apparently doesn't have "time" or "otim" command */
6730           cps->sendTime = 0;
6731           return;
6732         }
6733         if (StrStr(message, "analyze")) {
6734             cps->analysisSupport = FALSE;
6735             cps->analyzing = FALSE;
6736             Reset(FALSE, TRUE);
6737             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6738             DisplayError(buf2, 0);
6739             return;
6740         }
6741         if (StrStr(message, "(no matching move)st")) {
6742           /* Special kludge for GNU Chess 4 only */
6743           cps->stKludge = TRUE;
6744           SendTimeControl(cps, movesPerSession, timeControl,
6745                           timeIncrement, appData.searchDepth,
6746                           searchTime);
6747           return;
6748         }
6749         if (StrStr(message, "(no matching move)sd")) {
6750           /* Special kludge for GNU Chess 4 only */
6751           cps->sdKludge = TRUE;
6752           SendTimeControl(cps, movesPerSession, timeControl,
6753                           timeIncrement, appData.searchDepth,
6754                           searchTime);
6755           return;
6756         }
6757         if (!StrStr(message, "llegal")) {
6758             return;
6759         }
6760         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6761             gameMode == IcsIdle) return;
6762         if (forwardMostMove <= backwardMostMove) return;
6763         if (pausing) PauseEvent();
6764       if(appData.forceIllegal) {
6765             // [HGM] illegal: machine refused move; force position after move into it
6766           SendToProgram("force\n", cps);
6767           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6768                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6769                 // when black is to move, while there might be nothing on a2 or black
6770                 // might already have the move. So send the board as if white has the move.
6771                 // But first we must change the stm of the engine, as it refused the last move
6772                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6773                 if(WhiteOnMove(forwardMostMove)) {
6774                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6775                     SendBoard(cps, forwardMostMove); // kludgeless board
6776                 } else {
6777                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6778                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6779                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6780                 }
6781           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6782             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6783                  gameMode == TwoMachinesPlay)
6784               SendToProgram("go\n", cps);
6785             return;
6786       } else
6787         if (gameMode == PlayFromGameFile) {
6788             /* Stop reading this game file */
6789             gameMode = EditGame;
6790             ModeHighlight();
6791         }
6792         currentMove = --forwardMostMove;
6793         DisplayMove(currentMove-1); /* before DisplayMoveError */
6794         SwitchClocks();
6795         DisplayBothClocks();
6796         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6797                 parseList[currentMove], cps->which);
6798         DisplayMoveError(buf1);
6799         DrawPosition(FALSE, boards[currentMove]);
6800
6801         /* [HGM] illegal-move claim should forfeit game when Xboard */
6802         /* only passes fully legal moves                            */
6803         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6804             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6805                                 "False illegal-move claim", GE_XBOARD );
6806         }
6807         return;
6808     }
6809     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6810         /* Program has a broken "time" command that
6811            outputs a string not ending in newline.
6812            Don't use it. */
6813         cps->sendTime = 0;
6814     }
6815     
6816     /*
6817      * If chess program startup fails, exit with an error message.
6818      * Attempts to recover here are futile.
6819      */
6820     if ((StrStr(message, "unknown host") != NULL)
6821         || (StrStr(message, "No remote directory") != NULL)
6822         || (StrStr(message, "not found") != NULL)
6823         || (StrStr(message, "No such file") != NULL)
6824         || (StrStr(message, "can't alloc") != NULL)
6825         || (StrStr(message, "Permission denied") != NULL)) {
6826
6827         cps->maybeThinking = FALSE;
6828         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6829                 cps->which, cps->program, cps->host, message);
6830         RemoveInputSource(cps->isr);
6831         DisplayFatalError(buf1, 0, 1);
6832         return;
6833     }
6834     
6835     /* 
6836      * Look for hint output
6837      */
6838     if (sscanf(message, "Hint: %s", buf1) == 1) {
6839         if (cps == &first && hintRequested) {
6840             hintRequested = FALSE;
6841             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6842                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6843                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6844                                     PosFlags(forwardMostMove),
6845                                     fromY, fromX, toY, toX, promoChar, buf1);
6846                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6847                 DisplayInformation(buf2);
6848             } else {
6849                 /* Hint move could not be parsed!? */
6850               snprintf(buf2, sizeof(buf2),
6851                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6852                         buf1, cps->which);
6853                 DisplayError(buf2, 0);
6854             }
6855         } else {
6856             strcpy(lastHint, buf1);
6857         }
6858         return;
6859     }
6860
6861     /*
6862      * Ignore other messages if game is not in progress
6863      */
6864     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6865         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6866
6867     /*
6868      * look for win, lose, draw, or draw offer
6869      */
6870     if (strncmp(message, "1-0", 3) == 0) {
6871         char *p, *q, *r = "";
6872         p = strchr(message, '{');
6873         if (p) {
6874             q = strchr(p, '}');
6875             if (q) {
6876                 *q = NULLCHAR;
6877                 r = p + 1;
6878             }
6879         }
6880         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6881         return;
6882     } else if (strncmp(message, "0-1", 3) == 0) {
6883         char *p, *q, *r = "";
6884         p = strchr(message, '{');
6885         if (p) {
6886             q = strchr(p, '}');
6887             if (q) {
6888                 *q = NULLCHAR;
6889                 r = p + 1;
6890             }
6891         }
6892         /* Kludge for Arasan 4.1 bug */
6893         if (strcmp(r, "Black resigns") == 0) {
6894             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6895             return;
6896         }
6897         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6898         return;
6899     } else if (strncmp(message, "1/2", 3) == 0) {
6900         char *p, *q, *r = "";
6901         p = strchr(message, '{');
6902         if (p) {
6903             q = strchr(p, '}');
6904             if (q) {
6905                 *q = NULLCHAR;
6906                 r = p + 1;
6907             }
6908         }
6909             
6910         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6911         return;
6912
6913     } else if (strncmp(message, "White resign", 12) == 0) {
6914         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6915         return;
6916     } else if (strncmp(message, "Black resign", 12) == 0) {
6917         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6918         return;
6919     } else if (strncmp(message, "White matches", 13) == 0 ||
6920                strncmp(message, "Black matches", 13) == 0   ) {
6921         /* [HGM] ignore GNUShogi noises */
6922         return;
6923     } else if (strncmp(message, "White", 5) == 0 &&
6924                message[5] != '(' &&
6925                StrStr(message, "Black") == NULL) {
6926         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6927         return;
6928     } else if (strncmp(message, "Black", 5) == 0 &&
6929                message[5] != '(') {
6930         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6931         return;
6932     } else if (strcmp(message, "resign") == 0 ||
6933                strcmp(message, "computer resigns") == 0) {
6934         switch (gameMode) {
6935           case MachinePlaysBlack:
6936           case IcsPlayingBlack:
6937             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6938             break;
6939           case MachinePlaysWhite:
6940           case IcsPlayingWhite:
6941             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6942             break;
6943           case TwoMachinesPlay:
6944             if (cps->twoMachinesColor[0] == 'w')
6945               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6946             else
6947               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6948             break;
6949           default:
6950             /* can't happen */
6951             break;
6952         }
6953         return;
6954     } else if (strncmp(message, "opponent mates", 14) == 0) {
6955         switch (gameMode) {
6956           case MachinePlaysBlack:
6957           case IcsPlayingBlack:
6958             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6959             break;
6960           case MachinePlaysWhite:
6961           case IcsPlayingWhite:
6962             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6963             break;
6964           case TwoMachinesPlay:
6965             if (cps->twoMachinesColor[0] == 'w')
6966               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6967             else
6968               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6969             break;
6970           default:
6971             /* can't happen */
6972             break;
6973         }
6974         return;
6975     } else if (strncmp(message, "computer mates", 14) == 0) {
6976         switch (gameMode) {
6977           case MachinePlaysBlack:
6978           case IcsPlayingBlack:
6979             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6980             break;
6981           case MachinePlaysWhite:
6982           case IcsPlayingWhite:
6983             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6984             break;
6985           case TwoMachinesPlay:
6986             if (cps->twoMachinesColor[0] == 'w')
6987               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6988             else
6989               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6990             break;
6991           default:
6992             /* can't happen */
6993             break;
6994         }
6995         return;
6996     } else if (strncmp(message, "checkmate", 9) == 0) {
6997         if (WhiteOnMove(forwardMostMove)) {
6998             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6999         } else {
7000             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7001         }
7002         return;
7003     } else if (strstr(message, "Draw") != NULL ||
7004                strstr(message, "game is a draw") != NULL) {
7005         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7006         return;
7007     } else if (strstr(message, "offer") != NULL &&
7008                strstr(message, "draw") != NULL) {
7009 #if ZIPPY
7010         if (appData.zippyPlay && first.initDone) {
7011             /* Relay offer to ICS */
7012             SendToICS(ics_prefix);
7013             SendToICS("draw\n");
7014         }
7015 #endif
7016         cps->offeredDraw = 2; /* valid until this engine moves twice */
7017         if (gameMode == TwoMachinesPlay) {
7018             if (cps->other->offeredDraw) {
7019                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7020             /* [HGM] in two-machine mode we delay relaying draw offer      */
7021             /* until after we also have move, to see if it is really claim */
7022             }
7023         } else if (gameMode == MachinePlaysWhite ||
7024                    gameMode == MachinePlaysBlack) {
7025           if (userOfferedDraw) {
7026             DisplayInformation(_("Machine accepts your draw offer"));
7027             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7028           } else {
7029             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7030           }
7031         }
7032     }
7033
7034     
7035     /*
7036      * Look for thinking output
7037      */
7038     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7039           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7040                                 ) {
7041         int plylev, mvleft, mvtot, curscore, time;
7042         char mvname[MOVE_LEN];
7043         u64 nodes; // [DM]
7044         char plyext;
7045         int ignore = FALSE;
7046         int prefixHint = FALSE;
7047         mvname[0] = NULLCHAR;
7048
7049         switch (gameMode) {
7050           case MachinePlaysBlack:
7051           case IcsPlayingBlack:
7052             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7053             break;
7054           case MachinePlaysWhite:
7055           case IcsPlayingWhite:
7056             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7057             break;
7058           case AnalyzeMode:
7059           case AnalyzeFile:
7060             break;
7061           case IcsObserving: /* [DM] icsEngineAnalyze */
7062             if (!appData.icsEngineAnalyze) ignore = TRUE;
7063             break;
7064           case TwoMachinesPlay:
7065             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7066                 ignore = TRUE;
7067             }
7068             break;
7069           default:
7070             ignore = TRUE;
7071             break;
7072         }
7073
7074         if (!ignore) {
7075             buf1[0] = NULLCHAR;
7076             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7077                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7078
7079                 if (plyext != ' ' && plyext != '\t') {
7080                     time *= 100;
7081                 }
7082
7083                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7084                 if( cps->scoreIsAbsolute && 
7085                     ( gameMode == MachinePlaysBlack ||
7086                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7087                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7088                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7089                      !WhiteOnMove(currentMove)
7090                     ) )
7091                 {
7092                     curscore = -curscore;
7093                 }
7094
7095
7096                 programStats.depth = plylev;
7097                 programStats.nodes = nodes;
7098                 programStats.time = time;
7099                 programStats.score = curscore;
7100                 programStats.got_only_move = 0;
7101
7102                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7103                         int ticklen;
7104
7105                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7106                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7107                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7108                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7109                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7110                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7111                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7112                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7113                 }
7114
7115                 /* Buffer overflow protection */
7116                 if (buf1[0] != NULLCHAR) {
7117                     if (strlen(buf1) >= sizeof(programStats.movelist)
7118                         && appData.debugMode) {
7119                         fprintf(debugFP,
7120                                 "PV is too long; using the first %u bytes.\n",
7121                                 (unsigned) sizeof(programStats.movelist) - 1);
7122                     }
7123
7124                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7125                 } else {
7126                     sprintf(programStats.movelist, " no PV\n");
7127                 }
7128
7129                 if (programStats.seen_stat) {
7130                     programStats.ok_to_send = 1;
7131                 }
7132
7133                 if (strchr(programStats.movelist, '(') != NULL) {
7134                     programStats.line_is_book = 1;
7135                     programStats.nr_moves = 0;
7136                     programStats.moves_left = 0;
7137                 } else {
7138                     programStats.line_is_book = 0;
7139                 }
7140
7141                 SendProgramStatsToFrontend( cps, &programStats );
7142
7143                 /* 
7144                     [AS] Protect the thinkOutput buffer from overflow... this
7145                     is only useful if buf1 hasn't overflowed first!
7146                 */
7147                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7148                         plylev, 
7149                         (gameMode == TwoMachinesPlay ?
7150                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7151                         ((double) curscore) / 100.0,
7152                         prefixHint ? lastHint : "",
7153                         prefixHint ? " " : "" );
7154
7155                 if( buf1[0] != NULLCHAR ) {
7156                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7157
7158                     if( strlen(buf1) > max_len ) {
7159                         if( appData.debugMode) {
7160                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7161                         }
7162                         buf1[max_len+1] = '\0';
7163                     }
7164
7165                     strcat( thinkOutput, buf1 );
7166                 }
7167
7168                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7169                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7170                     DisplayMove(currentMove - 1);
7171                 }
7172                 return;
7173
7174             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7175                 /* crafty (9.25+) says "(only move) <move>"
7176                  * if there is only 1 legal move
7177                  */
7178                 sscanf(p, "(only move) %s", buf1);
7179                 sprintf(thinkOutput, "%s (only move)", buf1);
7180                 sprintf(programStats.movelist, "%s (only move)", buf1);
7181                 programStats.depth = 1;
7182                 programStats.nr_moves = 1;
7183                 programStats.moves_left = 1;
7184                 programStats.nodes = 1;
7185                 programStats.time = 1;
7186                 programStats.got_only_move = 1;
7187
7188                 /* Not really, but we also use this member to
7189                    mean "line isn't going to change" (Crafty
7190                    isn't searching, so stats won't change) */
7191                 programStats.line_is_book = 1;
7192
7193                 SendProgramStatsToFrontend( cps, &programStats );
7194                 
7195                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7196                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7197                     DisplayMove(currentMove - 1);
7198                 }
7199                 return;
7200             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7201                               &time, &nodes, &plylev, &mvleft,
7202                               &mvtot, mvname) >= 5) {
7203                 /* The stat01: line is from Crafty (9.29+) in response
7204                    to the "." command */
7205                 programStats.seen_stat = 1;
7206                 cps->maybeThinking = TRUE;
7207
7208                 if (programStats.got_only_move || !appData.periodicUpdates)
7209                   return;
7210
7211                 programStats.depth = plylev;
7212                 programStats.time = time;
7213                 programStats.nodes = nodes;
7214                 programStats.moves_left = mvleft;
7215                 programStats.nr_moves = mvtot;
7216                 strcpy(programStats.move_name, mvname);
7217                 programStats.ok_to_send = 1;
7218                 programStats.movelist[0] = '\0';
7219
7220                 SendProgramStatsToFrontend( cps, &programStats );
7221
7222                 return;
7223
7224             } else if (strncmp(message,"++",2) == 0) {
7225                 /* Crafty 9.29+ outputs this */
7226                 programStats.got_fail = 2;
7227                 return;
7228
7229             } else if (strncmp(message,"--",2) == 0) {
7230                 /* Crafty 9.29+ outputs this */
7231                 programStats.got_fail = 1;
7232                 return;
7233
7234             } else if (thinkOutput[0] != NULLCHAR &&
7235                        strncmp(message, "    ", 4) == 0) {
7236                 unsigned message_len;
7237
7238                 p = message;
7239                 while (*p && *p == ' ') p++;
7240
7241                 message_len = strlen( p );
7242
7243                 /* [AS] Avoid buffer overflow */
7244                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7245                     strcat(thinkOutput, " ");
7246                     strcat(thinkOutput, p);
7247                 }
7248
7249                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7250                     strcat(programStats.movelist, " ");
7251                     strcat(programStats.movelist, p);
7252                 }
7253
7254                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7255                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7256                     DisplayMove(currentMove - 1);
7257                 }
7258                 return;
7259             }
7260         }
7261         else {
7262             buf1[0] = NULLCHAR;
7263
7264             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7265                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7266             {
7267                 ChessProgramStats cpstats;
7268
7269                 if (plyext != ' ' && plyext != '\t') {
7270                     time *= 100;
7271                 }
7272
7273                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7274                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7275                     curscore = -curscore;
7276                 }
7277
7278                 cpstats.depth = plylev;
7279                 cpstats.nodes = nodes;
7280                 cpstats.time = time;
7281                 cpstats.score = curscore;
7282                 cpstats.got_only_move = 0;
7283                 cpstats.movelist[0] = '\0';
7284
7285                 if (buf1[0] != NULLCHAR) {
7286                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7287                 }
7288
7289                 cpstats.ok_to_send = 0;
7290                 cpstats.line_is_book = 0;
7291                 cpstats.nr_moves = 0;
7292                 cpstats.moves_left = 0;
7293
7294                 SendProgramStatsToFrontend( cps, &cpstats );
7295             }
7296         }
7297     }
7298 }
7299
7300
7301 /* Parse a game score from the character string "game", and
7302    record it as the history of the current game.  The game
7303    score is NOT assumed to start from the standard position. 
7304    The display is not updated in any way.
7305    */
7306 void
7307 ParseGameHistory(game)
7308      char *game;
7309 {
7310     ChessMove moveType;
7311     int fromX, fromY, toX, toY, boardIndex;
7312     char promoChar;
7313     char *p, *q;
7314     char buf[MSG_SIZ];
7315
7316     if (appData.debugMode)
7317       fprintf(debugFP, "Parsing game history: %s\n", game);
7318
7319     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7320     gameInfo.site = StrSave(appData.icsHost);
7321     gameInfo.date = PGNDate();
7322     gameInfo.round = StrSave("-");
7323
7324     /* Parse out names of players */
7325     while (*game == ' ') game++;
7326     p = buf;
7327     while (*game != ' ') *p++ = *game++;
7328     *p = NULLCHAR;
7329     gameInfo.white = StrSave(buf);
7330     while (*game == ' ') game++;
7331     p = buf;
7332     while (*game != ' ' && *game != '\n') *p++ = *game++;
7333     *p = NULLCHAR;
7334     gameInfo.black = StrSave(buf);
7335
7336     /* Parse moves */
7337     boardIndex = blackPlaysFirst ? 1 : 0;
7338     yynewstr(game);
7339     for (;;) {
7340         yyboardindex = boardIndex;
7341         moveType = (ChessMove) yylex();
7342         switch (moveType) {
7343           case IllegalMove:             /* maybe suicide chess, etc. */
7344   if (appData.debugMode) {
7345     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7346     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7347     setbuf(debugFP, NULL);
7348   }
7349           case WhitePromotionChancellor:
7350           case BlackPromotionChancellor:
7351           case WhitePromotionArchbishop:
7352           case BlackPromotionArchbishop:
7353           case WhitePromotionQueen:
7354           case BlackPromotionQueen:
7355           case WhitePromotionRook:
7356           case BlackPromotionRook:
7357           case WhitePromotionBishop:
7358           case BlackPromotionBishop:
7359           case WhitePromotionKnight:
7360           case BlackPromotionKnight:
7361           case WhitePromotionKing:
7362           case BlackPromotionKing:
7363           case NormalMove:
7364           case WhiteCapturesEnPassant:
7365           case BlackCapturesEnPassant:
7366           case WhiteKingSideCastle:
7367           case WhiteQueenSideCastle:
7368           case BlackKingSideCastle:
7369           case BlackQueenSideCastle:
7370           case WhiteKingSideCastleWild:
7371           case WhiteQueenSideCastleWild:
7372           case BlackKingSideCastleWild:
7373           case BlackQueenSideCastleWild:
7374           /* PUSH Fabien */
7375           case WhiteHSideCastleFR:
7376           case WhiteASideCastleFR:
7377           case BlackHSideCastleFR:
7378           case BlackASideCastleFR:
7379           /* POP Fabien */
7380             fromX = currentMoveString[0] - AAA;
7381             fromY = currentMoveString[1] - ONE;
7382             toX = currentMoveString[2] - AAA;
7383             toY = currentMoveString[3] - ONE;
7384             promoChar = currentMoveString[4];
7385             break;
7386           case WhiteDrop:
7387           case BlackDrop:
7388             fromX = moveType == WhiteDrop ?
7389               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7390             (int) CharToPiece(ToLower(currentMoveString[0]));
7391             fromY = DROP_RANK;
7392             toX = currentMoveString[2] - AAA;
7393             toY = currentMoveString[3] - ONE;
7394             promoChar = NULLCHAR;
7395             break;
7396           case AmbiguousMove:
7397             /* bug? */
7398             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7399   if (appData.debugMode) {
7400     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7401     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7402     setbuf(debugFP, NULL);
7403   }
7404             DisplayError(buf, 0);
7405             return;
7406           case ImpossibleMove:
7407             /* bug? */
7408             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7409   if (appData.debugMode) {
7410     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7411     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7412     setbuf(debugFP, NULL);
7413   }
7414             DisplayError(buf, 0);
7415             return;
7416           case (ChessMove) 0:   /* end of file */
7417             if (boardIndex < backwardMostMove) {
7418                 /* Oops, gap.  How did that happen? */
7419                 DisplayError(_("Gap in move list"), 0);
7420                 return;
7421             }
7422             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7423             if (boardIndex > forwardMostMove) {
7424                 forwardMostMove = boardIndex;
7425             }
7426             return;
7427           case ElapsedTime:
7428             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7429                 strcat(parseList[boardIndex-1], " ");
7430                 strcat(parseList[boardIndex-1], yy_text);
7431             }
7432             continue;
7433           case Comment:
7434           case PGNTag:
7435           case NAG:
7436           default:
7437             /* ignore */
7438             continue;
7439           case WhiteWins:
7440           case BlackWins:
7441           case GameIsDrawn:
7442           case GameUnfinished:
7443             if (gameMode == IcsExamining) {
7444                 if (boardIndex < backwardMostMove) {
7445                     /* Oops, gap.  How did that happen? */
7446                     return;
7447                 }
7448                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7449                 return;
7450             }
7451             gameInfo.result = moveType;
7452             p = strchr(yy_text, '{');
7453             if (p == NULL) p = strchr(yy_text, '(');
7454             if (p == NULL) {
7455                 p = yy_text;
7456                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7457             } else {
7458                 q = strchr(p, *p == '{' ? '}' : ')');
7459                 if (q != NULL) *q = NULLCHAR;
7460                 p++;
7461             }
7462             gameInfo.resultDetails = StrSave(p);
7463             continue;
7464         }
7465         if (boardIndex >= forwardMostMove &&
7466             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7467             backwardMostMove = blackPlaysFirst ? 1 : 0;
7468             return;
7469         }
7470         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7471                                  fromY, fromX, toY, toX, promoChar,
7472                                  parseList[boardIndex]);
7473         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7474         /* currentMoveString is set as a side-effect of yylex */
7475         strcpy(moveList[boardIndex], currentMoveString);
7476         strcat(moveList[boardIndex], "\n");
7477         boardIndex++;
7478         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7479         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7480           case MT_NONE:
7481           case MT_STALEMATE:
7482           default:
7483             break;
7484           case MT_CHECK:
7485             if(gameInfo.variant != VariantShogi)
7486                 strcat(parseList[boardIndex - 1], "+");
7487             break;
7488           case MT_CHECKMATE:
7489           case MT_STAINMATE:
7490             strcat(parseList[boardIndex - 1], "#");
7491             break;
7492         }
7493     }
7494 }
7495
7496
7497 /* Apply a move to the given board  */
7498 void
7499 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7500      int fromX, fromY, toX, toY;
7501      int promoChar;
7502      Board board;
7503 {
7504   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7505
7506     /* [HGM] compute & store e.p. status and castling rights for new position */
7507     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7508     { int i;
7509
7510       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7511       oldEP = (signed char)board[EP_STATUS];
7512       board[EP_STATUS] = EP_NONE;
7513
7514       if( board[toY][toX] != EmptySquare ) 
7515            board[EP_STATUS] = EP_CAPTURE;  
7516
7517       if( board[fromY][fromX] == WhitePawn ) {
7518            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7519                board[EP_STATUS] = EP_PAWN_MOVE;
7520            if( toY-fromY==2) {
7521                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7522                         gameInfo.variant != VariantBerolina || toX < fromX)
7523                       board[EP_STATUS] = toX | berolina;
7524                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7525                         gameInfo.variant != VariantBerolina || toX > fromX) 
7526                       board[EP_STATUS] = toX;
7527            }
7528       } else 
7529       if( board[fromY][fromX] == BlackPawn ) {
7530            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7531                board[EP_STATUS] = EP_PAWN_MOVE; 
7532            if( toY-fromY== -2) {
7533                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7534                         gameInfo.variant != VariantBerolina || toX < fromX)
7535                       board[EP_STATUS] = toX | berolina;
7536                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7537                         gameInfo.variant != VariantBerolina || toX > fromX) 
7538                       board[EP_STATUS] = toX;
7539            }
7540        }
7541
7542        for(i=0; i<nrCastlingRights; i++) {
7543            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7544               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7545              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7546        }
7547
7548     }
7549
7550   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7551   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7552        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7553          
7554   if (fromX == toX && fromY == toY) return;
7555
7556   if (fromY == DROP_RANK) {
7557         /* must be first */
7558         piece = board[toY][toX] = (ChessSquare) fromX;
7559   } else {
7560      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7561      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7562      if(gameInfo.variant == VariantKnightmate)
7563          king += (int) WhiteUnicorn - (int) WhiteKing;
7564
7565     /* Code added by Tord: */
7566     /* FRC castling assumed when king captures friendly rook. */
7567     if (board[fromY][fromX] == WhiteKing &&
7568              board[toY][toX] == WhiteRook) {
7569       board[fromY][fromX] = EmptySquare;
7570       board[toY][toX] = EmptySquare;
7571       if(toX > fromX) {
7572         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7573       } else {
7574         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7575       }
7576     } else if (board[fromY][fromX] == BlackKing &&
7577                board[toY][toX] == BlackRook) {
7578       board[fromY][fromX] = EmptySquare;
7579       board[toY][toX] = EmptySquare;
7580       if(toX > fromX) {
7581         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7582       } else {
7583         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7584       }
7585     /* End of code added by Tord */
7586
7587     } else if (board[fromY][fromX] == king
7588         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7589         && toY == fromY && toX > fromX+1) {
7590         board[fromY][fromX] = EmptySquare;
7591         board[toY][toX] = king;
7592         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7593         board[fromY][BOARD_RGHT-1] = EmptySquare;
7594     } else if (board[fromY][fromX] == king
7595         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7596                && toY == fromY && toX < fromX-1) {
7597         board[fromY][fromX] = EmptySquare;
7598         board[toY][toX] = king;
7599         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7600         board[fromY][BOARD_LEFT] = EmptySquare;
7601     } else if (board[fromY][fromX] == WhitePawn
7602                && toY == BOARD_HEIGHT-1
7603                && gameInfo.variant != VariantXiangqi
7604                ) {
7605         /* white pawn promotion */
7606         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7607         if (board[toY][toX] == EmptySquare) {
7608             board[toY][toX] = WhiteQueen;
7609         }
7610         if(gameInfo.variant==VariantBughouse ||
7611            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7612             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7613         board[fromY][fromX] = EmptySquare;
7614     } else if ((fromY == BOARD_HEIGHT-4)
7615                && (toX != fromX)
7616                && gameInfo.variant != VariantXiangqi
7617                && gameInfo.variant != VariantBerolina
7618                && (board[fromY][fromX] == WhitePawn)
7619                && (board[toY][toX] == EmptySquare)) {
7620         board[fromY][fromX] = EmptySquare;
7621         board[toY][toX] = WhitePawn;
7622         captured = board[toY - 1][toX];
7623         board[toY - 1][toX] = EmptySquare;
7624     } else if ((fromY == BOARD_HEIGHT-4)
7625                && (toX == fromX)
7626                && gameInfo.variant == VariantBerolina
7627                && (board[fromY][fromX] == WhitePawn)
7628                && (board[toY][toX] == EmptySquare)) {
7629         board[fromY][fromX] = EmptySquare;
7630         board[toY][toX] = WhitePawn;
7631         if(oldEP & EP_BEROLIN_A) {
7632                 captured = board[fromY][fromX-1];
7633                 board[fromY][fromX-1] = EmptySquare;
7634         }else{  captured = board[fromY][fromX+1];
7635                 board[fromY][fromX+1] = EmptySquare;
7636         }
7637     } else if (board[fromY][fromX] == king
7638         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7639                && toY == fromY && toX > fromX+1) {
7640         board[fromY][fromX] = EmptySquare;
7641         board[toY][toX] = king;
7642         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7643         board[fromY][BOARD_RGHT-1] = EmptySquare;
7644     } else if (board[fromY][fromX] == king
7645         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7646                && toY == fromY && toX < fromX-1) {
7647         board[fromY][fromX] = EmptySquare;
7648         board[toY][toX] = king;
7649         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7650         board[fromY][BOARD_LEFT] = EmptySquare;
7651     } else if (fromY == 7 && fromX == 3
7652                && board[fromY][fromX] == BlackKing
7653                && toY == 7 && toX == 5) {
7654         board[fromY][fromX] = EmptySquare;
7655         board[toY][toX] = BlackKing;
7656         board[fromY][7] = EmptySquare;
7657         board[toY][4] = BlackRook;
7658     } else if (fromY == 7 && fromX == 3
7659                && board[fromY][fromX] == BlackKing
7660                && toY == 7 && toX == 1) {
7661         board[fromY][fromX] = EmptySquare;
7662         board[toY][toX] = BlackKing;
7663         board[fromY][0] = EmptySquare;
7664         board[toY][2] = BlackRook;
7665     } else if (board[fromY][fromX] == BlackPawn
7666                && toY == 0
7667                && gameInfo.variant != VariantXiangqi
7668                ) {
7669         /* black pawn promotion */
7670         board[0][toX] = CharToPiece(ToLower(promoChar));
7671         if (board[0][toX] == EmptySquare) {
7672             board[0][toX] = BlackQueen;
7673         }
7674         if(gameInfo.variant==VariantBughouse ||
7675            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7676             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7677         board[fromY][fromX] = EmptySquare;
7678     } else if ((fromY == 3)
7679                && (toX != fromX)
7680                && gameInfo.variant != VariantXiangqi
7681                && gameInfo.variant != VariantBerolina
7682                && (board[fromY][fromX] == BlackPawn)
7683                && (board[toY][toX] == EmptySquare)) {
7684         board[fromY][fromX] = EmptySquare;
7685         board[toY][toX] = BlackPawn;
7686         captured = board[toY + 1][toX];
7687         board[toY + 1][toX] = EmptySquare;
7688     } else if ((fromY == 3)
7689                && (toX == fromX)
7690                && gameInfo.variant == VariantBerolina
7691                && (board[fromY][fromX] == BlackPawn)
7692                && (board[toY][toX] == EmptySquare)) {
7693         board[fromY][fromX] = EmptySquare;
7694         board[toY][toX] = BlackPawn;
7695         if(oldEP & EP_BEROLIN_A) {
7696                 captured = board[fromY][fromX-1];
7697                 board[fromY][fromX-1] = EmptySquare;
7698         }else{  captured = board[fromY][fromX+1];
7699                 board[fromY][fromX+1] = EmptySquare;
7700         }
7701     } else {
7702         board[toY][toX] = board[fromY][fromX];
7703         board[fromY][fromX] = EmptySquare;
7704     }
7705
7706     /* [HGM] now we promote for Shogi, if needed */
7707     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7708         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7709   }
7710
7711     if (gameInfo.holdingsWidth != 0) {
7712
7713       /* !!A lot more code needs to be written to support holdings  */
7714       /* [HGM] OK, so I have written it. Holdings are stored in the */
7715       /* penultimate board files, so they are automaticlly stored   */
7716       /* in the game history.                                       */
7717       if (fromY == DROP_RANK) {
7718         /* Delete from holdings, by decreasing count */
7719         /* and erasing image if necessary            */
7720         p = (int) fromX;
7721         if(p < (int) BlackPawn) { /* white drop */
7722              p -= (int)WhitePawn;
7723                  p = PieceToNumber((ChessSquare)p);
7724              if(p >= gameInfo.holdingsSize) p = 0;
7725              if(--board[p][BOARD_WIDTH-2] <= 0)
7726                   board[p][BOARD_WIDTH-1] = EmptySquare;
7727              if((int)board[p][BOARD_WIDTH-2] < 0)
7728                         board[p][BOARD_WIDTH-2] = 0;
7729         } else {                  /* black drop */
7730              p -= (int)BlackPawn;
7731                  p = PieceToNumber((ChessSquare)p);
7732              if(p >= gameInfo.holdingsSize) p = 0;
7733              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7734                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7735              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7736                         board[BOARD_HEIGHT-1-p][1] = 0;
7737         }
7738       }
7739       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7740           && gameInfo.variant != VariantBughouse        ) {
7741         /* [HGM] holdings: Add to holdings, if holdings exist */
7742         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7743                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7744                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7745         }
7746         p = (int) captured;
7747         if (p >= (int) BlackPawn) {
7748           p -= (int)BlackPawn;
7749           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7750                   /* in Shogi restore piece to its original  first */
7751                   captured = (ChessSquare) (DEMOTED captured);
7752                   p = DEMOTED p;
7753           }
7754           p = PieceToNumber((ChessSquare)p);
7755           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7756           board[p][BOARD_WIDTH-2]++;
7757           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7758         } else {
7759           p -= (int)WhitePawn;
7760           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7761                   captured = (ChessSquare) (DEMOTED captured);
7762                   p = DEMOTED p;
7763           }
7764           p = PieceToNumber((ChessSquare)p);
7765           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7766           board[BOARD_HEIGHT-1-p][1]++;
7767           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7768         }
7769       }
7770     } else if (gameInfo.variant == VariantAtomic) {
7771       if (captured != EmptySquare) {
7772         int y, x;
7773         for (y = toY-1; y <= toY+1; y++) {
7774           for (x = toX-1; x <= toX+1; x++) {
7775             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7776                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7777               board[y][x] = EmptySquare;
7778             }
7779           }
7780         }
7781         board[toY][toX] = EmptySquare;
7782       }
7783     }
7784     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7785         /* [HGM] Shogi promotions */
7786         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7787     }
7788
7789     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7790                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7791         // [HGM] superchess: take promotion piece out of holdings
7792         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7793         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7794             if(!--board[k][BOARD_WIDTH-2])
7795                 board[k][BOARD_WIDTH-1] = EmptySquare;
7796         } else {
7797             if(!--board[BOARD_HEIGHT-1-k][1])
7798                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7799         }
7800     }
7801
7802 }
7803
7804 /* Updates forwardMostMove */
7805 void
7806 MakeMove(fromX, fromY, toX, toY, promoChar)
7807      int fromX, fromY, toX, toY;
7808      int promoChar;
7809 {
7810 //    forwardMostMove++; // [HGM] bare: moved downstream
7811
7812     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7813         int timeLeft; static int lastLoadFlag=0; int king, piece;
7814         piece = boards[forwardMostMove][fromY][fromX];
7815         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7816         if(gameInfo.variant == VariantKnightmate)
7817             king += (int) WhiteUnicorn - (int) WhiteKing;
7818         if(forwardMostMove == 0) {
7819             if(blackPlaysFirst) 
7820                 fprintf(serverMoves, "%s;", second.tidy);
7821             fprintf(serverMoves, "%s;", first.tidy);
7822             if(!blackPlaysFirst) 
7823                 fprintf(serverMoves, "%s;", second.tidy);
7824         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7825         lastLoadFlag = loadFlag;
7826         // print base move
7827         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7828         // print castling suffix
7829         if( toY == fromY && piece == king ) {
7830             if(toX-fromX > 1)
7831                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7832             if(fromX-toX >1)
7833                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7834         }
7835         // e.p. suffix
7836         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7837              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7838              boards[forwardMostMove][toY][toX] == EmptySquare
7839              && fromX != toX )
7840                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7841         // promotion suffix
7842         if(promoChar != NULLCHAR)
7843                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7844         if(!loadFlag) {
7845             fprintf(serverMoves, "/%d/%d",
7846                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7847             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7848             else                      timeLeft = blackTimeRemaining/1000;
7849             fprintf(serverMoves, "/%d", timeLeft);
7850         }
7851         fflush(serverMoves);
7852     }
7853
7854     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7855       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7856                         0, 1);
7857       return;
7858     }
7859     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7860     if (commentList[forwardMostMove+1] != NULL) {
7861         free(commentList[forwardMostMove+1]);
7862         commentList[forwardMostMove+1] = NULL;
7863     }
7864     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7865     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7866     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7867     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7868     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7869     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7870     gameInfo.result = GameUnfinished;
7871     if (gameInfo.resultDetails != NULL) {
7872         free(gameInfo.resultDetails);
7873         gameInfo.resultDetails = NULL;
7874     }
7875     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7876                               moveList[forwardMostMove - 1]);
7877     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7878                              PosFlags(forwardMostMove - 1),
7879                              fromY, fromX, toY, toX, promoChar,
7880                              parseList[forwardMostMove - 1]);
7881     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7882       case MT_NONE:
7883       case MT_STALEMATE:
7884       default:
7885         break;
7886       case MT_CHECK:
7887         if(gameInfo.variant != VariantShogi)
7888             strcat(parseList[forwardMostMove - 1], "+");
7889         break;
7890       case MT_CHECKMATE:
7891       case MT_STAINMATE:
7892         strcat(parseList[forwardMostMove - 1], "#");
7893         break;
7894     }
7895     if (appData.debugMode) {
7896         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7897     }
7898
7899 }
7900
7901 /* Updates currentMove if not pausing */
7902 void
7903 ShowMove(fromX, fromY, toX, toY)
7904 {
7905     int instant = (gameMode == PlayFromGameFile) ?
7906         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7907     if(appData.noGUI) return;
7908     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7909         if (!instant) {
7910             if (forwardMostMove == currentMove + 1) {
7911                 AnimateMove(boards[forwardMostMove - 1],
7912                             fromX, fromY, toX, toY);
7913             }
7914             if (appData.highlightLastMove) {
7915                 SetHighlights(fromX, fromY, toX, toY);
7916             }
7917         }
7918         currentMove = forwardMostMove;
7919     }
7920
7921     if (instant) return;
7922
7923     DisplayMove(currentMove - 1);
7924     DrawPosition(FALSE, boards[currentMove]);
7925     DisplayBothClocks();
7926     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7927 }
7928
7929 void SendEgtPath(ChessProgramState *cps)
7930 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7931         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7932
7933         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7934
7935         while(*p) {
7936             char c, *q = name+1, *r, *s;
7937
7938             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7939             while(*p && *p != ',') *q++ = *p++;
7940             *q++ = ':'; *q = 0;
7941             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7942                 strcmp(name, ",nalimov:") == 0 ) {
7943                 // take nalimov path from the menu-changeable option first, if it is defined
7944                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7945                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7946             } else
7947             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7948                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7949                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7950                 s = r = StrStr(s, ":") + 1; // beginning of path info
7951                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7952                 c = *r; *r = 0;             // temporarily null-terminate path info
7953                     *--q = 0;               // strip of trailig ':' from name
7954                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7955                 *r = c;
7956                 SendToProgram(buf,cps);     // send egtbpath command for this format
7957             }
7958             if(*p == ',') p++; // read away comma to position for next format name
7959         }
7960 }
7961
7962 void
7963 InitChessProgram(cps, setup)
7964      ChessProgramState *cps;
7965      int setup; /* [HGM] needed to setup FRC opening position */
7966 {
7967     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7968     if (appData.noChessProgram) return;
7969     hintRequested = FALSE;
7970     bookRequested = FALSE;
7971
7972     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7973     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7974     if(cps->memSize) { /* [HGM] memory */
7975         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7976         SendToProgram(buf, cps);
7977     }
7978     SendEgtPath(cps); /* [HGM] EGT */
7979     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7980         sprintf(buf, "cores %d\n", appData.smpCores);
7981         SendToProgram(buf, cps);
7982     }
7983
7984     SendToProgram(cps->initString, cps);
7985     if (gameInfo.variant != VariantNormal &&
7986         gameInfo.variant != VariantLoadable
7987         /* [HGM] also send variant if board size non-standard */
7988         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7989                                             ) {
7990       char *v = VariantName(gameInfo.variant);
7991       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7992         /* [HGM] in protocol 1 we have to assume all variants valid */
7993         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7994         DisplayFatalError(buf, 0, 1);
7995         return;
7996       }
7997
7998       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7999       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8000       if( gameInfo.variant == VariantXiangqi )
8001            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8002       if( gameInfo.variant == VariantShogi )
8003            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8004       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8005            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8006       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8007                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8008            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8009       if( gameInfo.variant == VariantCourier )
8010            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8011       if( gameInfo.variant == VariantSuper )
8012            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8013       if( gameInfo.variant == VariantGreat )
8014            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8015
8016       if(overruled) {
8017            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8018                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8019            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8020            if(StrStr(cps->variants, b) == NULL) { 
8021                // specific sized variant not known, check if general sizing allowed
8022                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8023                    if(StrStr(cps->variants, "boardsize") == NULL) {
8024                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8025                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8026                        DisplayFatalError(buf, 0, 1);
8027                        return;
8028                    }
8029                    /* [HGM] here we really should compare with the maximum supported board size */
8030                }
8031            }
8032       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8033       sprintf(buf, "variant %s\n", b);
8034       SendToProgram(buf, cps);
8035     }
8036     currentlyInitializedVariant = gameInfo.variant;
8037
8038     /* [HGM] send opening position in FRC to first engine */
8039     if(setup) {
8040           SendToProgram("force\n", cps);
8041           SendBoard(cps, 0);
8042           /* engine is now in force mode! Set flag to wake it up after first move. */
8043           setboardSpoiledMachineBlack = 1;
8044     }
8045
8046     if (cps->sendICS) {
8047       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8048       SendToProgram(buf, cps);
8049     }
8050     cps->maybeThinking = FALSE;
8051     cps->offeredDraw = 0;
8052     if (!appData.icsActive) {
8053         SendTimeControl(cps, movesPerSession, timeControl,
8054                         timeIncrement, appData.searchDepth,
8055                         searchTime);
8056     }
8057     if (appData.showThinking 
8058         // [HGM] thinking: four options require thinking output to be sent
8059         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8060                                 ) {
8061         SendToProgram("post\n", cps);
8062     }
8063     SendToProgram("hard\n", cps);
8064     if (!appData.ponderNextMove) {
8065         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8066            it without being sure what state we are in first.  "hard"
8067            is not a toggle, so that one is OK.
8068          */
8069         SendToProgram("easy\n", cps);
8070     }
8071     if (cps->usePing) {
8072       sprintf(buf, "ping %d\n", ++cps->lastPing);
8073       SendToProgram(buf, cps);
8074     }
8075     cps->initDone = TRUE;
8076 }   
8077
8078
8079 void
8080 StartChessProgram(cps)
8081      ChessProgramState *cps;
8082 {
8083     char buf[MSG_SIZ];
8084     int err;
8085
8086     if (appData.noChessProgram) return;
8087     cps->initDone = FALSE;
8088
8089     if (strcmp(cps->host, "localhost") == 0) {
8090         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8091     } else if (*appData.remoteShell == NULLCHAR) {
8092         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8093     } else {
8094         if (*appData.remoteUser == NULLCHAR) {
8095           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8096                     cps->program);
8097         } else {
8098           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8099                     cps->host, appData.remoteUser, cps->program);
8100         }
8101         err = StartChildProcess(buf, "", &cps->pr);
8102     }
8103     
8104     if (err != 0) {
8105         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8106         DisplayFatalError(buf, err, 1);
8107         cps->pr = NoProc;
8108         cps->isr = NULL;
8109         return;
8110     }
8111     
8112     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8113     if (cps->protocolVersion > 1) {
8114       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8115       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8116       cps->comboCnt = 0;  //                and values of combo boxes
8117       SendToProgram(buf, cps);
8118     } else {
8119       SendToProgram("xboard\n", cps);
8120     }
8121 }
8122
8123
8124 void
8125 TwoMachinesEventIfReady P((void))
8126 {
8127   if (first.lastPing != first.lastPong) {
8128     DisplayMessage("", _("Waiting for first chess program"));
8129     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8130     return;
8131   }
8132   if (second.lastPing != second.lastPong) {
8133     DisplayMessage("", _("Waiting for second chess program"));
8134     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8135     return;
8136   }
8137   ThawUI();
8138   TwoMachinesEvent();
8139 }
8140
8141 void
8142 NextMatchGame P((void))
8143 {
8144     int index; /* [HGM] autoinc: step load index during match */
8145     Reset(FALSE, TRUE);
8146     if (*appData.loadGameFile != NULLCHAR) {
8147         index = appData.loadGameIndex;
8148         if(index < 0) { // [HGM] autoinc
8149             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8150             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8151         } 
8152         LoadGameFromFile(appData.loadGameFile,
8153                          index,
8154                          appData.loadGameFile, FALSE);
8155     } else if (*appData.loadPositionFile != NULLCHAR) {
8156         index = appData.loadPositionIndex;
8157         if(index < 0) { // [HGM] autoinc
8158             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8159             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8160         } 
8161         LoadPositionFromFile(appData.loadPositionFile,
8162                              index,
8163                              appData.loadPositionFile);
8164     }
8165     TwoMachinesEventIfReady();
8166 }
8167
8168 void UserAdjudicationEvent( int result )
8169 {
8170     ChessMove gameResult = GameIsDrawn;
8171
8172     if( result > 0 ) {
8173         gameResult = WhiteWins;
8174     }
8175     else if( result < 0 ) {
8176         gameResult = BlackWins;
8177     }
8178
8179     if( gameMode == TwoMachinesPlay ) {
8180         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8181     }
8182 }
8183
8184
8185 // [HGM] save: calculate checksum of game to make games easily identifiable
8186 int StringCheckSum(char *s)
8187 {
8188         int i = 0;
8189         if(s==NULL) return 0;
8190         while(*s) i = i*259 + *s++;
8191         return i;
8192 }
8193
8194 int GameCheckSum()
8195 {
8196         int i, sum=0;
8197         for(i=backwardMostMove; i<forwardMostMove; i++) {
8198                 sum += pvInfoList[i].depth;
8199                 sum += StringCheckSum(parseList[i]);
8200                 sum += StringCheckSum(commentList[i]);
8201                 sum *= 261;
8202         }
8203         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8204         return sum + StringCheckSum(commentList[i]);
8205 } // end of save patch
8206
8207 void
8208 GameEnds(result, resultDetails, whosays)
8209      ChessMove result;
8210      char *resultDetails;
8211      int whosays;
8212 {
8213     GameMode nextGameMode;
8214     int isIcsGame;
8215     char buf[MSG_SIZ];
8216
8217     if(endingGame) return; /* [HGM] crash: forbid recursion */
8218     endingGame = 1;
8219
8220     if (appData.debugMode) {
8221       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8222               result, resultDetails ? resultDetails : "(null)", whosays);
8223     }
8224
8225     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8226         /* If we are playing on ICS, the server decides when the
8227            game is over, but the engine can offer to draw, claim 
8228            a draw, or resign. 
8229          */
8230 #if ZIPPY
8231         if (appData.zippyPlay && first.initDone) {
8232             if (result == GameIsDrawn) {
8233                 /* In case draw still needs to be claimed */
8234                 SendToICS(ics_prefix);
8235                 SendToICS("draw\n");
8236             } else if (StrCaseStr(resultDetails, "resign")) {
8237                 SendToICS(ics_prefix);
8238                 SendToICS("resign\n");
8239             }
8240         }
8241 #endif
8242         endingGame = 0; /* [HGM] crash */
8243         return;
8244     }
8245
8246     /* If we're loading the game from a file, stop */
8247     if (whosays == GE_FILE) {
8248       (void) StopLoadGameTimer();
8249       gameFileFP = NULL;
8250     }
8251
8252     /* Cancel draw offers */
8253     first.offeredDraw = second.offeredDraw = 0;
8254
8255     /* If this is an ICS game, only ICS can really say it's done;
8256        if not, anyone can. */
8257     isIcsGame = (gameMode == IcsPlayingWhite || 
8258                  gameMode == IcsPlayingBlack || 
8259                  gameMode == IcsObserving    || 
8260                  gameMode == IcsExamining);
8261
8262     if (!isIcsGame || whosays == GE_ICS) {
8263         /* OK -- not an ICS game, or ICS said it was done */
8264         StopClocks();
8265         if (!isIcsGame && !appData.noChessProgram) 
8266           SetUserThinkingEnables();
8267     
8268         /* [HGM] if a machine claims the game end we verify this claim */
8269         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8270             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8271                 char claimer;
8272                 ChessMove trueResult = (ChessMove) -1;
8273
8274                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8275                                             first.twoMachinesColor[0] :
8276                                             second.twoMachinesColor[0] ;
8277
8278                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8279                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8280                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8281                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8282                 } else
8283                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8284                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8285                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8286                 } else
8287                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8288                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8289                 }
8290
8291                 // now verify win claims, but not in drop games, as we don't understand those yet
8292                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8293                                                  || gameInfo.variant == VariantGreat) &&
8294                     (result == WhiteWins && claimer == 'w' ||
8295                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8296                       if (appData.debugMode) {
8297                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8298                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8299                       }
8300                       if(result != trueResult) {
8301                               sprintf(buf, "False win claim: '%s'", resultDetails);
8302                               result = claimer == 'w' ? BlackWins : WhiteWins;
8303                               resultDetails = buf;
8304                       }
8305                 } else
8306                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8307                     && (forwardMostMove <= backwardMostMove ||
8308                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8309                         (claimer=='b')==(forwardMostMove&1))
8310                                                                                   ) {
8311                       /* [HGM] verify: draws that were not flagged are false claims */
8312                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8313                       result = claimer == 'w' ? BlackWins : WhiteWins;
8314                       resultDetails = buf;
8315                 }
8316                 /* (Claiming a loss is accepted no questions asked!) */
8317             }
8318             /* [HGM] bare: don't allow bare King to win */
8319             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8320                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8321                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8322                && result != GameIsDrawn)
8323             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8324                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8325                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8326                         if(p >= 0 && p <= (int)WhiteKing) k++;
8327                 }
8328                 if (appData.debugMode) {
8329                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8330                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8331                 }
8332                 if(k <= 1) {
8333                         result = GameIsDrawn;
8334                         sprintf(buf, "%s but bare king", resultDetails);
8335                         resultDetails = buf;
8336                 }
8337             }
8338         }
8339
8340
8341         if(serverMoves != NULL && !loadFlag) { char c = '=';
8342             if(result==WhiteWins) c = '+';
8343             if(result==BlackWins) c = '-';
8344             if(resultDetails != NULL)
8345                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8346         }
8347         if (resultDetails != NULL) {
8348             gameInfo.result = result;
8349             gameInfo.resultDetails = StrSave(resultDetails);
8350
8351             /* display last move only if game was not loaded from file */
8352             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8353                 DisplayMove(currentMove - 1);
8354     
8355             if (forwardMostMove != 0) {
8356                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8357                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8358                                                                 ) {
8359                     if (*appData.saveGameFile != NULLCHAR) {
8360                         SaveGameToFile(appData.saveGameFile, TRUE);
8361                     } else if (appData.autoSaveGames) {
8362                         AutoSaveGame();
8363                     }
8364                     if (*appData.savePositionFile != NULLCHAR) {
8365                         SavePositionToFile(appData.savePositionFile);
8366                     }
8367                 }
8368             }
8369
8370             /* Tell program how game ended in case it is learning */
8371             /* [HGM] Moved this to after saving the PGN, just in case */
8372             /* engine died and we got here through time loss. In that */
8373             /* case we will get a fatal error writing the pipe, which */
8374             /* would otherwise lose us the PGN.                       */
8375             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8376             /* output during GameEnds should never be fatal anymore   */
8377             if (gameMode == MachinePlaysWhite ||
8378                 gameMode == MachinePlaysBlack ||
8379                 gameMode == TwoMachinesPlay ||
8380                 gameMode == IcsPlayingWhite ||
8381                 gameMode == IcsPlayingBlack ||
8382                 gameMode == BeginningOfGame) {
8383                 char buf[MSG_SIZ];
8384                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8385                         resultDetails);
8386                 if (first.pr != NoProc) {
8387                     SendToProgram(buf, &first);
8388                 }
8389                 if (second.pr != NoProc &&
8390                     gameMode == TwoMachinesPlay) {
8391                     SendToProgram(buf, &second);
8392                 }
8393             }
8394         }
8395
8396         if (appData.icsActive) {
8397             if (appData.quietPlay &&
8398                 (gameMode == IcsPlayingWhite ||
8399                  gameMode == IcsPlayingBlack)) {
8400                 SendToICS(ics_prefix);
8401                 SendToICS("set shout 1\n");
8402             }
8403             nextGameMode = IcsIdle;
8404             ics_user_moved = FALSE;
8405             /* clean up premove.  It's ugly when the game has ended and the
8406              * premove highlights are still on the board.
8407              */
8408             if (gotPremove) {
8409               gotPremove = FALSE;
8410               ClearPremoveHighlights();
8411               DrawPosition(FALSE, boards[currentMove]);
8412             }
8413             if (whosays == GE_ICS) {
8414                 switch (result) {
8415                 case WhiteWins:
8416                     if (gameMode == IcsPlayingWhite)
8417                         PlayIcsWinSound();
8418                     else if(gameMode == IcsPlayingBlack)
8419                         PlayIcsLossSound();
8420                     break;
8421                 case BlackWins:
8422                     if (gameMode == IcsPlayingBlack)
8423                         PlayIcsWinSound();
8424                     else if(gameMode == IcsPlayingWhite)
8425                         PlayIcsLossSound();
8426                     break;
8427                 case GameIsDrawn:
8428                     PlayIcsDrawSound();
8429                     break;
8430                 default:
8431                     PlayIcsUnfinishedSound();
8432                 }
8433             }
8434         } else if (gameMode == EditGame ||
8435                    gameMode == PlayFromGameFile || 
8436                    gameMode == AnalyzeMode || 
8437                    gameMode == AnalyzeFile) {
8438             nextGameMode = gameMode;
8439         } else {
8440             nextGameMode = EndOfGame;
8441         }
8442         pausing = FALSE;
8443         ModeHighlight();
8444     } else {
8445         nextGameMode = gameMode;
8446     }
8447
8448     if (appData.noChessProgram) {
8449         gameMode = nextGameMode;
8450         ModeHighlight();
8451         endingGame = 0; /* [HGM] crash */
8452         return;
8453     }
8454
8455     if (first.reuse) {
8456         /* Put first chess program into idle state */
8457         if (first.pr != NoProc &&
8458             (gameMode == MachinePlaysWhite ||
8459              gameMode == MachinePlaysBlack ||
8460              gameMode == TwoMachinesPlay ||
8461              gameMode == IcsPlayingWhite ||
8462              gameMode == IcsPlayingBlack ||
8463              gameMode == BeginningOfGame)) {
8464             SendToProgram("force\n", &first);
8465             if (first.usePing) {
8466               char buf[MSG_SIZ];
8467               sprintf(buf, "ping %d\n", ++first.lastPing);
8468               SendToProgram(buf, &first);
8469             }
8470         }
8471     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8472         /* Kill off first chess program */
8473         if (first.isr != NULL)
8474           RemoveInputSource(first.isr);
8475         first.isr = NULL;
8476     
8477         if (first.pr != NoProc) {
8478             ExitAnalyzeMode();
8479             DoSleep( appData.delayBeforeQuit );
8480             SendToProgram("quit\n", &first);
8481             DoSleep( appData.delayAfterQuit );
8482             DestroyChildProcess(first.pr, first.useSigterm);
8483         }
8484         first.pr = NoProc;
8485     }
8486     if (second.reuse) {
8487         /* Put second chess program into idle state */
8488         if (second.pr != NoProc &&
8489             gameMode == TwoMachinesPlay) {
8490             SendToProgram("force\n", &second);
8491             if (second.usePing) {
8492               char buf[MSG_SIZ];
8493               sprintf(buf, "ping %d\n", ++second.lastPing);
8494               SendToProgram(buf, &second);
8495             }
8496         }
8497     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8498         /* Kill off second chess program */
8499         if (second.isr != NULL)
8500           RemoveInputSource(second.isr);
8501         second.isr = NULL;
8502     
8503         if (second.pr != NoProc) {
8504             DoSleep( appData.delayBeforeQuit );
8505             SendToProgram("quit\n", &second);
8506             DoSleep( appData.delayAfterQuit );
8507             DestroyChildProcess(second.pr, second.useSigterm);
8508         }
8509         second.pr = NoProc;
8510     }
8511
8512     if (matchMode && gameMode == TwoMachinesPlay) {
8513         switch (result) {
8514         case WhiteWins:
8515           if (first.twoMachinesColor[0] == 'w') {
8516             first.matchWins++;
8517           } else {
8518             second.matchWins++;
8519           }
8520           break;
8521         case BlackWins:
8522           if (first.twoMachinesColor[0] == 'b') {
8523             first.matchWins++;
8524           } else {
8525             second.matchWins++;
8526           }
8527           break;
8528         default:
8529           break;
8530         }
8531         if (matchGame < appData.matchGames) {
8532             char *tmp;
8533             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8534                 tmp = first.twoMachinesColor;
8535                 first.twoMachinesColor = second.twoMachinesColor;
8536                 second.twoMachinesColor = tmp;
8537             }
8538             gameMode = nextGameMode;
8539             matchGame++;
8540             if(appData.matchPause>10000 || appData.matchPause<10)
8541                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8542             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8543             endingGame = 0; /* [HGM] crash */
8544             return;
8545         } else {
8546             char buf[MSG_SIZ];
8547             gameMode = nextGameMode;
8548             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8549                     first.tidy, second.tidy,
8550                     first.matchWins, second.matchWins,
8551                     appData.matchGames - (first.matchWins + second.matchWins));
8552             DisplayFatalError(buf, 0, 0);
8553         }
8554     }
8555     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8556         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8557       ExitAnalyzeMode();
8558     gameMode = nextGameMode;
8559     ModeHighlight();
8560     endingGame = 0;  /* [HGM] crash */
8561 }
8562
8563 /* Assumes program was just initialized (initString sent).
8564    Leaves program in force mode. */
8565 void
8566 FeedMovesToProgram(cps, upto) 
8567      ChessProgramState *cps;
8568      int upto;
8569 {
8570     int i;
8571     
8572     if (appData.debugMode)
8573       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8574               startedFromSetupPosition ? "position and " : "",
8575               backwardMostMove, upto, cps->which);
8576     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8577         // [HGM] variantswitch: make engine aware of new variant
8578         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8579                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8580         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8581         SendToProgram(buf, cps);
8582         currentlyInitializedVariant = gameInfo.variant;
8583     }
8584     SendToProgram("force\n", cps);
8585     if (startedFromSetupPosition) {
8586         SendBoard(cps, backwardMostMove);
8587     if (appData.debugMode) {
8588         fprintf(debugFP, "feedMoves\n");
8589     }
8590     }
8591     for (i = backwardMostMove; i < upto; i++) {
8592         SendMoveToProgram(i, cps);
8593     }
8594 }
8595
8596
8597 void
8598 ResurrectChessProgram()
8599 {
8600      /* The chess program may have exited.
8601         If so, restart it and feed it all the moves made so far. */
8602
8603     if (appData.noChessProgram || first.pr != NoProc) return;
8604     
8605     StartChessProgram(&first);
8606     InitChessProgram(&first, FALSE);
8607     FeedMovesToProgram(&first, currentMove);
8608
8609     if (!first.sendTime) {
8610         /* can't tell gnuchess what its clock should read,
8611            so we bow to its notion. */
8612         ResetClocks();
8613         timeRemaining[0][currentMove] = whiteTimeRemaining;
8614         timeRemaining[1][currentMove] = blackTimeRemaining;
8615     }
8616
8617     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8618                 appData.icsEngineAnalyze) && first.analysisSupport) {
8619       SendToProgram("analyze\n", &first);
8620       first.analyzing = TRUE;
8621     }
8622 }
8623
8624 /*
8625  * Button procedures
8626  */
8627 void
8628 Reset(redraw, init)
8629      int redraw, init;
8630 {
8631     int i;
8632
8633     if (appData.debugMode) {
8634         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8635                 redraw, init, gameMode);
8636     }
8637     CleanupTail(); // [HGM] vari: delete any stored variations
8638     pausing = pauseExamInvalid = FALSE;
8639     startedFromSetupPosition = blackPlaysFirst = FALSE;
8640     firstMove = TRUE;
8641     whiteFlag = blackFlag = FALSE;
8642     userOfferedDraw = FALSE;
8643     hintRequested = bookRequested = FALSE;
8644     first.maybeThinking = FALSE;
8645     second.maybeThinking = FALSE;
8646     first.bookSuspend = FALSE; // [HGM] book
8647     second.bookSuspend = FALSE;
8648     thinkOutput[0] = NULLCHAR;
8649     lastHint[0] = NULLCHAR;
8650     ClearGameInfo(&gameInfo);
8651     gameInfo.variant = StringToVariant(appData.variant);
8652     ics_user_moved = ics_clock_paused = FALSE;
8653     ics_getting_history = H_FALSE;
8654     ics_gamenum = -1;
8655     white_holding[0] = black_holding[0] = NULLCHAR;
8656     ClearProgramStats();
8657     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8658     
8659     ResetFrontEnd();
8660     ClearHighlights();
8661     flipView = appData.flipView;
8662     ClearPremoveHighlights();
8663     gotPremove = FALSE;
8664     alarmSounded = FALSE;
8665
8666     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8667     if(appData.serverMovesName != NULL) {
8668         /* [HGM] prepare to make moves file for broadcasting */
8669         clock_t t = clock();
8670         if(serverMoves != NULL) fclose(serverMoves);
8671         serverMoves = fopen(appData.serverMovesName, "r");
8672         if(serverMoves != NULL) {
8673             fclose(serverMoves);
8674             /* delay 15 sec before overwriting, so all clients can see end */
8675             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8676         }
8677         serverMoves = fopen(appData.serverMovesName, "w");
8678     }
8679
8680     ExitAnalyzeMode();
8681     gameMode = BeginningOfGame;
8682     ModeHighlight();
8683     if(appData.icsActive) gameInfo.variant = VariantNormal;
8684     currentMove = forwardMostMove = backwardMostMove = 0;
8685     InitPosition(redraw);
8686     for (i = 0; i < MAX_MOVES; i++) {
8687         if (commentList[i] != NULL) {
8688             free(commentList[i]);
8689             commentList[i] = NULL;
8690         }
8691     }
8692     ResetClocks();
8693     timeRemaining[0][0] = whiteTimeRemaining;
8694     timeRemaining[1][0] = blackTimeRemaining;
8695     if (first.pr == NULL) {
8696         StartChessProgram(&first);
8697     }
8698     if (init) {
8699             InitChessProgram(&first, startedFromSetupPosition);
8700     }
8701     DisplayTitle("");
8702     DisplayMessage("", "");
8703     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8704     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8705 }
8706
8707 void
8708 AutoPlayGameLoop()
8709 {
8710     for (;;) {
8711         if (!AutoPlayOneMove())
8712           return;
8713         if (matchMode || appData.timeDelay == 0)
8714           continue;
8715         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8716           return;
8717         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8718         break;
8719     }
8720 }
8721
8722
8723 int
8724 AutoPlayOneMove()
8725 {
8726     int fromX, fromY, toX, toY;
8727
8728     if (appData.debugMode) {
8729       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8730     }
8731
8732     if (gameMode != PlayFromGameFile)
8733       return FALSE;
8734
8735     if (currentMove >= forwardMostMove) {
8736       gameMode = EditGame;
8737       ModeHighlight();
8738
8739       /* [AS] Clear current move marker at the end of a game */
8740       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8741
8742       return FALSE;
8743     }
8744     
8745     toX = moveList[currentMove][2] - AAA;
8746     toY = moveList[currentMove][3] - ONE;
8747
8748     if (moveList[currentMove][1] == '@') {
8749         if (appData.highlightLastMove) {
8750             SetHighlights(-1, -1, toX, toY);
8751         }
8752     } else {
8753         fromX = moveList[currentMove][0] - AAA;
8754         fromY = moveList[currentMove][1] - ONE;
8755
8756         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8757
8758         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8759
8760         if (appData.highlightLastMove) {
8761             SetHighlights(fromX, fromY, toX, toY);
8762         }
8763     }
8764     DisplayMove(currentMove);
8765     SendMoveToProgram(currentMove++, &first);
8766     DisplayBothClocks();
8767     DrawPosition(FALSE, boards[currentMove]);
8768     // [HGM] PV info: always display, routine tests if empty
8769     DisplayComment(currentMove - 1, commentList[currentMove]);
8770     return TRUE;
8771 }
8772
8773
8774 int
8775 LoadGameOneMove(readAhead)
8776      ChessMove readAhead;
8777 {
8778     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8779     char promoChar = NULLCHAR;
8780     ChessMove moveType;
8781     char move[MSG_SIZ];
8782     char *p, *q;
8783     
8784     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8785         gameMode != AnalyzeMode && gameMode != Training) {
8786         gameFileFP = NULL;
8787         return FALSE;
8788     }
8789     
8790     yyboardindex = forwardMostMove;
8791     if (readAhead != (ChessMove)0) {
8792       moveType = readAhead;
8793     } else {
8794       if (gameFileFP == NULL)
8795           return FALSE;
8796       moveType = (ChessMove) yylex();
8797     }
8798     
8799     done = FALSE;
8800     switch (moveType) {
8801       case Comment:
8802         if (appData.debugMode) 
8803           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8804         p = yy_text;
8805
8806         /* append the comment but don't display it */
8807         AppendComment(currentMove, p, FALSE);
8808         return TRUE;
8809
8810       case WhiteCapturesEnPassant:
8811       case BlackCapturesEnPassant:
8812       case WhitePromotionChancellor:
8813       case BlackPromotionChancellor:
8814       case WhitePromotionArchbishop:
8815       case BlackPromotionArchbishop:
8816       case WhitePromotionCentaur:
8817       case BlackPromotionCentaur:
8818       case WhitePromotionQueen:
8819       case BlackPromotionQueen:
8820       case WhitePromotionRook:
8821       case BlackPromotionRook:
8822       case WhitePromotionBishop:
8823       case BlackPromotionBishop:
8824       case WhitePromotionKnight:
8825       case BlackPromotionKnight:
8826       case WhitePromotionKing:
8827       case BlackPromotionKing:
8828       case NormalMove:
8829       case WhiteKingSideCastle:
8830       case WhiteQueenSideCastle:
8831       case BlackKingSideCastle:
8832       case BlackQueenSideCastle:
8833       case WhiteKingSideCastleWild:
8834       case WhiteQueenSideCastleWild:
8835       case BlackKingSideCastleWild:
8836       case BlackQueenSideCastleWild:
8837       /* PUSH Fabien */
8838       case WhiteHSideCastleFR:
8839       case WhiteASideCastleFR:
8840       case BlackHSideCastleFR:
8841       case BlackASideCastleFR:
8842       /* POP Fabien */
8843         if (appData.debugMode)
8844           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8845         fromX = currentMoveString[0] - AAA;
8846         fromY = currentMoveString[1] - ONE;
8847         toX = currentMoveString[2] - AAA;
8848         toY = currentMoveString[3] - ONE;
8849         promoChar = currentMoveString[4];
8850         break;
8851
8852       case WhiteDrop:
8853       case BlackDrop:
8854         if (appData.debugMode)
8855           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8856         fromX = moveType == WhiteDrop ?
8857           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8858         (int) CharToPiece(ToLower(currentMoveString[0]));
8859         fromY = DROP_RANK;
8860         toX = currentMoveString[2] - AAA;
8861         toY = currentMoveString[3] - ONE;
8862         break;
8863
8864       case WhiteWins:
8865       case BlackWins:
8866       case GameIsDrawn:
8867       case GameUnfinished:
8868         if (appData.debugMode)
8869           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8870         p = strchr(yy_text, '{');
8871         if (p == NULL) p = strchr(yy_text, '(');
8872         if (p == NULL) {
8873             p = yy_text;
8874             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8875         } else {
8876             q = strchr(p, *p == '{' ? '}' : ')');
8877             if (q != NULL) *q = NULLCHAR;
8878             p++;
8879         }
8880         GameEnds(moveType, p, GE_FILE);
8881         done = TRUE;
8882         if (cmailMsgLoaded) {
8883             ClearHighlights();
8884             flipView = WhiteOnMove(currentMove);
8885             if (moveType == GameUnfinished) flipView = !flipView;
8886             if (appData.debugMode)
8887               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8888         }
8889         break;
8890
8891       case (ChessMove) 0:       /* end of file */
8892         if (appData.debugMode)
8893           fprintf(debugFP, "Parser hit end of file\n");
8894         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8895           case MT_NONE:
8896           case MT_CHECK:
8897             break;
8898           case MT_CHECKMATE:
8899           case MT_STAINMATE:
8900             if (WhiteOnMove(currentMove)) {
8901                 GameEnds(BlackWins, "Black mates", GE_FILE);
8902             } else {
8903                 GameEnds(WhiteWins, "White mates", GE_FILE);
8904             }
8905             break;
8906           case MT_STALEMATE:
8907             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8908             break;
8909         }
8910         done = TRUE;
8911         break;
8912
8913       case MoveNumberOne:
8914         if (lastLoadGameStart == GNUChessGame) {
8915             /* GNUChessGames have numbers, but they aren't move numbers */
8916             if (appData.debugMode)
8917               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8918                       yy_text, (int) moveType);
8919             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8920         }
8921         /* else fall thru */
8922
8923       case XBoardGame:
8924       case GNUChessGame:
8925       case PGNTag:
8926         /* Reached start of next game in file */
8927         if (appData.debugMode)
8928           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8929         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8930           case MT_NONE:
8931           case MT_CHECK:
8932             break;
8933           case MT_CHECKMATE:
8934           case MT_STAINMATE:
8935             if (WhiteOnMove(currentMove)) {
8936                 GameEnds(BlackWins, "Black mates", GE_FILE);
8937             } else {
8938                 GameEnds(WhiteWins, "White mates", GE_FILE);
8939             }
8940             break;
8941           case MT_STALEMATE:
8942             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8943             break;
8944         }
8945         done = TRUE;
8946         break;
8947
8948       case PositionDiagram:     /* should not happen; ignore */
8949       case ElapsedTime:         /* ignore */
8950       case NAG:                 /* ignore */
8951         if (appData.debugMode)
8952           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8953                   yy_text, (int) moveType);
8954         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8955
8956       case IllegalMove:
8957         if (appData.testLegality) {
8958             if (appData.debugMode)
8959               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8960             sprintf(move, _("Illegal move: %d.%s%s"),
8961                     (forwardMostMove / 2) + 1,
8962                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8963             DisplayError(move, 0);
8964             done = TRUE;
8965         } else {
8966             if (appData.debugMode)
8967               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8968                       yy_text, currentMoveString);
8969             fromX = currentMoveString[0] - AAA;
8970             fromY = currentMoveString[1] - ONE;
8971             toX = currentMoveString[2] - AAA;
8972             toY = currentMoveString[3] - ONE;
8973             promoChar = currentMoveString[4];
8974         }
8975         break;
8976
8977       case AmbiguousMove:
8978         if (appData.debugMode)
8979           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8980         sprintf(move, _("Ambiguous move: %d.%s%s"),
8981                 (forwardMostMove / 2) + 1,
8982                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8983         DisplayError(move, 0);
8984         done = TRUE;
8985         break;
8986
8987       default:
8988       case ImpossibleMove:
8989         if (appData.debugMode)
8990           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8991         sprintf(move, _("Illegal move: %d.%s%s"),
8992                 (forwardMostMove / 2) + 1,
8993                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8994         DisplayError(move, 0);
8995         done = TRUE;
8996         break;
8997     }
8998
8999     if (done) {
9000         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9001             DrawPosition(FALSE, boards[currentMove]);
9002             DisplayBothClocks();
9003             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9004               DisplayComment(currentMove - 1, commentList[currentMove]);
9005         }
9006         (void) StopLoadGameTimer();
9007         gameFileFP = NULL;
9008         cmailOldMove = forwardMostMove;
9009         return FALSE;
9010     } else {
9011         /* currentMoveString is set as a side-effect of yylex */
9012         strcat(currentMoveString, "\n");
9013         strcpy(moveList[forwardMostMove], currentMoveString);
9014         
9015         thinkOutput[0] = NULLCHAR;
9016         MakeMove(fromX, fromY, toX, toY, promoChar);
9017         currentMove = forwardMostMove;
9018         return TRUE;
9019     }
9020 }
9021
9022 /* Load the nth game from the given file */
9023 int
9024 LoadGameFromFile(filename, n, title, useList)
9025      char *filename;
9026      int n;
9027      char *title;
9028      /*Boolean*/ int useList;
9029 {
9030     FILE *f;
9031     char buf[MSG_SIZ];
9032
9033     if (strcmp(filename, "-") == 0) {
9034         f = stdin;
9035         title = "stdin";
9036     } else {
9037         f = fopen(filename, "rb");
9038         if (f == NULL) {
9039           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9040             DisplayError(buf, errno);
9041             return FALSE;
9042         }
9043     }
9044     if (fseek(f, 0, 0) == -1) {
9045         /* f is not seekable; probably a pipe */
9046         useList = FALSE;
9047     }
9048     if (useList && n == 0) {
9049         int error = GameListBuild(f);
9050         if (error) {
9051             DisplayError(_("Cannot build game list"), error);
9052         } else if (!ListEmpty(&gameList) &&
9053                    ((ListGame *) gameList.tailPred)->number > 1) {
9054             GameListPopUp(f, title);
9055             return TRUE;
9056         }
9057         GameListDestroy();
9058         n = 1;
9059     }
9060     if (n == 0) n = 1;
9061     return LoadGame(f, n, title, FALSE);
9062 }
9063
9064
9065 void
9066 MakeRegisteredMove()
9067 {
9068     int fromX, fromY, toX, toY;
9069     char promoChar;
9070     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9071         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9072           case CMAIL_MOVE:
9073           case CMAIL_DRAW:
9074             if (appData.debugMode)
9075               fprintf(debugFP, "Restoring %s for game %d\n",
9076                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9077     
9078             thinkOutput[0] = NULLCHAR;
9079             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9080             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9081             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9082             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9083             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9084             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9085             MakeMove(fromX, fromY, toX, toY, promoChar);
9086             ShowMove(fromX, fromY, toX, toY);
9087               
9088             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9089               case MT_NONE:
9090               case MT_CHECK:
9091                 break;
9092                 
9093               case MT_CHECKMATE:
9094               case MT_STAINMATE:
9095                 if (WhiteOnMove(currentMove)) {
9096                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9097                 } else {
9098                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9099                 }
9100                 break;
9101                 
9102               case MT_STALEMATE:
9103                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9104                 break;
9105             }
9106
9107             break;
9108             
9109           case CMAIL_RESIGN:
9110             if (WhiteOnMove(currentMove)) {
9111                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9112             } else {
9113                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9114             }
9115             break;
9116             
9117           case CMAIL_ACCEPT:
9118             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9119             break;
9120               
9121           default:
9122             break;
9123         }
9124     }
9125
9126     return;
9127 }
9128
9129 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9130 int
9131 CmailLoadGame(f, gameNumber, title, useList)
9132      FILE *f;
9133      int gameNumber;
9134      char *title;
9135      int useList;
9136 {
9137     int retVal;
9138
9139     if (gameNumber > nCmailGames) {
9140         DisplayError(_("No more games in this message"), 0);
9141         return FALSE;
9142     }
9143     if (f == lastLoadGameFP) {
9144         int offset = gameNumber - lastLoadGameNumber;
9145         if (offset == 0) {
9146             cmailMsg[0] = NULLCHAR;
9147             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9148                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9149                 nCmailMovesRegistered--;
9150             }
9151             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9152             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9153                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9154             }
9155         } else {
9156             if (! RegisterMove()) return FALSE;
9157         }
9158     }
9159
9160     retVal = LoadGame(f, gameNumber, title, useList);
9161
9162     /* Make move registered during previous look at this game, if any */
9163     MakeRegisteredMove();
9164
9165     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9166         commentList[currentMove]
9167           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9168         DisplayComment(currentMove - 1, commentList[currentMove]);
9169     }
9170
9171     return retVal;
9172 }
9173
9174 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9175 int
9176 ReloadGame(offset)
9177      int offset;
9178 {
9179     int gameNumber = lastLoadGameNumber + offset;
9180     if (lastLoadGameFP == NULL) {
9181         DisplayError(_("No game has been loaded yet"), 0);
9182         return FALSE;
9183     }
9184     if (gameNumber <= 0) {
9185         DisplayError(_("Can't back up any further"), 0);
9186         return FALSE;
9187     }
9188     if (cmailMsgLoaded) {
9189         return CmailLoadGame(lastLoadGameFP, gameNumber,
9190                              lastLoadGameTitle, lastLoadGameUseList);
9191     } else {
9192         return LoadGame(lastLoadGameFP, gameNumber,
9193                         lastLoadGameTitle, lastLoadGameUseList);
9194     }
9195 }
9196
9197
9198
9199 /* Load the nth game from open file f */
9200 int
9201 LoadGame(f, gameNumber, title, useList)
9202      FILE *f;
9203      int gameNumber;
9204      char *title;
9205      int useList;
9206 {
9207     ChessMove cm;
9208     char buf[MSG_SIZ];
9209     int gn = gameNumber;
9210     ListGame *lg = NULL;
9211     int numPGNTags = 0;
9212     int err;
9213     GameMode oldGameMode;
9214     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9215
9216     if (appData.debugMode) 
9217         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9218
9219     if (gameMode == Training )
9220         SetTrainingModeOff();
9221
9222     oldGameMode = gameMode;
9223     if (gameMode != BeginningOfGame) {
9224       Reset(FALSE, TRUE);
9225     }
9226
9227     gameFileFP = f;
9228     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9229         fclose(lastLoadGameFP);
9230     }
9231
9232     if (useList) {
9233         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9234         
9235         if (lg) {
9236             fseek(f, lg->offset, 0);
9237             GameListHighlight(gameNumber);
9238             gn = 1;
9239         }
9240         else {
9241             DisplayError(_("Game number out of range"), 0);
9242             return FALSE;
9243         }
9244     } else {
9245         GameListDestroy();
9246         if (fseek(f, 0, 0) == -1) {
9247             if (f == lastLoadGameFP ?
9248                 gameNumber == lastLoadGameNumber + 1 :
9249                 gameNumber == 1) {
9250                 gn = 1;
9251             } else {
9252                 DisplayError(_("Can't seek on game file"), 0);
9253                 return FALSE;
9254             }
9255         }
9256     }
9257     lastLoadGameFP = f;
9258     lastLoadGameNumber = gameNumber;
9259     strcpy(lastLoadGameTitle, title);
9260     lastLoadGameUseList = useList;
9261
9262     yynewfile(f);
9263
9264     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9265       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9266                 lg->gameInfo.black);
9267             DisplayTitle(buf);
9268     } else if (*title != NULLCHAR) {
9269         if (gameNumber > 1) {
9270             sprintf(buf, "%s %d", title, gameNumber);
9271             DisplayTitle(buf);
9272         } else {
9273             DisplayTitle(title);
9274         }
9275     }
9276
9277     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9278         gameMode = PlayFromGameFile;
9279         ModeHighlight();
9280     }
9281
9282     currentMove = forwardMostMove = backwardMostMove = 0;
9283     CopyBoard(boards[0], initialPosition);
9284     StopClocks();
9285
9286     /*
9287      * Skip the first gn-1 games in the file.
9288      * Also skip over anything that precedes an identifiable 
9289      * start of game marker, to avoid being confused by 
9290      * garbage at the start of the file.  Currently 
9291      * recognized start of game markers are the move number "1",
9292      * the pattern "gnuchess .* game", the pattern
9293      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9294      * A game that starts with one of the latter two patterns
9295      * will also have a move number 1, possibly
9296      * following a position diagram.
9297      * 5-4-02: Let's try being more lenient and allowing a game to
9298      * start with an unnumbered move.  Does that break anything?
9299      */
9300     cm = lastLoadGameStart = (ChessMove) 0;
9301     while (gn > 0) {
9302         yyboardindex = forwardMostMove;
9303         cm = (ChessMove) yylex();
9304         switch (cm) {
9305           case (ChessMove) 0:
9306             if (cmailMsgLoaded) {
9307                 nCmailGames = CMAIL_MAX_GAMES - gn;
9308             } else {
9309                 Reset(TRUE, TRUE);
9310                 DisplayError(_("Game not found in file"), 0);
9311             }
9312             return FALSE;
9313
9314           case GNUChessGame:
9315           case XBoardGame:
9316             gn--;
9317             lastLoadGameStart = cm;
9318             break;
9319             
9320           case MoveNumberOne:
9321             switch (lastLoadGameStart) {
9322               case GNUChessGame:
9323               case XBoardGame:
9324               case PGNTag:
9325                 break;
9326               case MoveNumberOne:
9327               case (ChessMove) 0:
9328                 gn--;           /* count this game */
9329                 lastLoadGameStart = cm;
9330                 break;
9331               default:
9332                 /* impossible */
9333                 break;
9334             }
9335             break;
9336
9337           case PGNTag:
9338             switch (lastLoadGameStart) {
9339               case GNUChessGame:
9340               case PGNTag:
9341               case MoveNumberOne:
9342               case (ChessMove) 0:
9343                 gn--;           /* count this game */
9344                 lastLoadGameStart = cm;
9345                 break;
9346               case XBoardGame:
9347                 lastLoadGameStart = cm; /* game counted already */
9348                 break;
9349               default:
9350                 /* impossible */
9351                 break;
9352             }
9353             if (gn > 0) {
9354                 do {
9355                     yyboardindex = forwardMostMove;
9356                     cm = (ChessMove) yylex();
9357                 } while (cm == PGNTag || cm == Comment);
9358             }
9359             break;
9360
9361           case WhiteWins:
9362           case BlackWins:
9363           case GameIsDrawn:
9364             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9365                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9366                     != CMAIL_OLD_RESULT) {
9367                     nCmailResults ++ ;
9368                     cmailResult[  CMAIL_MAX_GAMES
9369                                 - gn - 1] = CMAIL_OLD_RESULT;
9370                 }
9371             }
9372             break;
9373
9374           case NormalMove:
9375             /* Only a NormalMove can be at the start of a game
9376              * without a position diagram. */
9377             if (lastLoadGameStart == (ChessMove) 0) {
9378               gn--;
9379               lastLoadGameStart = MoveNumberOne;
9380             }
9381             break;
9382
9383           default:
9384             break;
9385         }
9386     }
9387     
9388     if (appData.debugMode)
9389       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9390
9391     if (cm == XBoardGame) {
9392         /* Skip any header junk before position diagram and/or move 1 */
9393         for (;;) {
9394             yyboardindex = forwardMostMove;
9395             cm = (ChessMove) yylex();
9396
9397             if (cm == (ChessMove) 0 ||
9398                 cm == GNUChessGame || cm == XBoardGame) {
9399                 /* Empty game; pretend end-of-file and handle later */
9400                 cm = (ChessMove) 0;
9401                 break;
9402             }
9403
9404             if (cm == MoveNumberOne || cm == PositionDiagram ||
9405                 cm == PGNTag || cm == Comment)
9406               break;
9407         }
9408     } else if (cm == GNUChessGame) {
9409         if (gameInfo.event != NULL) {
9410             free(gameInfo.event);
9411         }
9412         gameInfo.event = StrSave(yy_text);
9413     }   
9414
9415     startedFromSetupPosition = FALSE;
9416     while (cm == PGNTag) {
9417         if (appData.debugMode) 
9418           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9419         err = ParsePGNTag(yy_text, &gameInfo);
9420         if (!err) numPGNTags++;
9421
9422         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9423         if(gameInfo.variant != oldVariant) {
9424             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9425             InitPosition(TRUE);
9426             oldVariant = gameInfo.variant;
9427             if (appData.debugMode) 
9428               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9429         }
9430
9431
9432         if (gameInfo.fen != NULL) {
9433           Board initial_position;
9434           startedFromSetupPosition = TRUE;
9435           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9436             Reset(TRUE, TRUE);
9437             DisplayError(_("Bad FEN position in file"), 0);
9438             return FALSE;
9439           }
9440           CopyBoard(boards[0], initial_position);
9441           if (blackPlaysFirst) {
9442             currentMove = forwardMostMove = backwardMostMove = 1;
9443             CopyBoard(boards[1], initial_position);
9444             strcpy(moveList[0], "");
9445             strcpy(parseList[0], "");
9446             timeRemaining[0][1] = whiteTimeRemaining;
9447             timeRemaining[1][1] = blackTimeRemaining;
9448             if (commentList[0] != NULL) {
9449               commentList[1] = commentList[0];
9450               commentList[0] = NULL;
9451             }
9452           } else {
9453             currentMove = forwardMostMove = backwardMostMove = 0;
9454           }
9455           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9456           {   int i;
9457               initialRulePlies = FENrulePlies;
9458               for( i=0; i< nrCastlingRights; i++ )
9459                   initialRights[i] = initial_position[CASTLING][i];
9460           }
9461           yyboardindex = forwardMostMove;
9462           free(gameInfo.fen);
9463           gameInfo.fen = NULL;
9464         }
9465
9466         yyboardindex = forwardMostMove;
9467         cm = (ChessMove) yylex();
9468
9469         /* Handle comments interspersed among the tags */
9470         while (cm == Comment) {
9471             char *p;
9472             if (appData.debugMode) 
9473               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9474             p = yy_text;
9475             AppendComment(currentMove, p, FALSE);
9476             yyboardindex = forwardMostMove;
9477             cm = (ChessMove) yylex();
9478         }
9479     }
9480
9481     /* don't rely on existence of Event tag since if game was
9482      * pasted from clipboard the Event tag may not exist
9483      */
9484     if (numPGNTags > 0){
9485         char *tags;
9486         if (gameInfo.variant == VariantNormal) {
9487           gameInfo.variant = StringToVariant(gameInfo.event);
9488         }
9489         if (!matchMode) {
9490           if( appData.autoDisplayTags ) {
9491             tags = PGNTags(&gameInfo);
9492             TagsPopUp(tags, CmailMsg());
9493             free(tags);
9494           }
9495         }
9496     } else {
9497         /* Make something up, but don't display it now */
9498         SetGameInfo();
9499         TagsPopDown();
9500     }
9501
9502     if (cm == PositionDiagram) {
9503         int i, j;
9504         char *p;
9505         Board initial_position;
9506
9507         if (appData.debugMode)
9508           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9509
9510         if (!startedFromSetupPosition) {
9511             p = yy_text;
9512             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9513               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9514                 switch (*p) {
9515                   case '[':
9516                   case '-':
9517                   case ' ':
9518                   case '\t':
9519                   case '\n':
9520                   case '\r':
9521                     break;
9522                   default:
9523                     initial_position[i][j++] = CharToPiece(*p);
9524                     break;
9525                 }
9526             while (*p == ' ' || *p == '\t' ||
9527                    *p == '\n' || *p == '\r') p++;
9528         
9529             if (strncmp(p, "black", strlen("black"))==0)
9530               blackPlaysFirst = TRUE;
9531             else
9532               blackPlaysFirst = FALSE;
9533             startedFromSetupPosition = TRUE;
9534         
9535             CopyBoard(boards[0], initial_position);
9536             if (blackPlaysFirst) {
9537                 currentMove = forwardMostMove = backwardMostMove = 1;
9538                 CopyBoard(boards[1], initial_position);
9539                 strcpy(moveList[0], "");
9540                 strcpy(parseList[0], "");
9541                 timeRemaining[0][1] = whiteTimeRemaining;
9542                 timeRemaining[1][1] = blackTimeRemaining;
9543                 if (commentList[0] != NULL) {
9544                     commentList[1] = commentList[0];
9545                     commentList[0] = NULL;
9546                 }
9547             } else {
9548                 currentMove = forwardMostMove = backwardMostMove = 0;
9549             }
9550         }
9551         yyboardindex = forwardMostMove;
9552         cm = (ChessMove) yylex();
9553     }
9554
9555     if (first.pr == NoProc) {
9556         StartChessProgram(&first);
9557     }
9558     InitChessProgram(&first, FALSE);
9559     SendToProgram("force\n", &first);
9560     if (startedFromSetupPosition) {
9561         SendBoard(&first, forwardMostMove);
9562     if (appData.debugMode) {
9563         fprintf(debugFP, "Load Game\n");
9564     }
9565         DisplayBothClocks();
9566     }      
9567
9568     /* [HGM] server: flag to write setup moves in broadcast file as one */
9569     loadFlag = appData.suppressLoadMoves;
9570
9571     while (cm == Comment) {
9572         char *p;
9573         if (appData.debugMode) 
9574           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9575         p = yy_text;
9576         AppendComment(currentMove, p, FALSE);
9577         yyboardindex = forwardMostMove;
9578         cm = (ChessMove) yylex();
9579     }
9580
9581     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9582         cm == WhiteWins || cm == BlackWins ||
9583         cm == GameIsDrawn || cm == GameUnfinished) {
9584         DisplayMessage("", _("No moves in game"));
9585         if (cmailMsgLoaded) {
9586             if (appData.debugMode)
9587               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9588             ClearHighlights();
9589             flipView = FALSE;
9590         }
9591         DrawPosition(FALSE, boards[currentMove]);
9592         DisplayBothClocks();
9593         gameMode = EditGame;
9594         ModeHighlight();
9595         gameFileFP = NULL;
9596         cmailOldMove = 0;
9597         return TRUE;
9598     }
9599
9600     // [HGM] PV info: routine tests if comment empty
9601     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9602         DisplayComment(currentMove - 1, commentList[currentMove]);
9603     }
9604     if (!matchMode && appData.timeDelay != 0) 
9605       DrawPosition(FALSE, boards[currentMove]);
9606
9607     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9608       programStats.ok_to_send = 1;
9609     }
9610
9611     /* if the first token after the PGN tags is a move
9612      * and not move number 1, retrieve it from the parser 
9613      */
9614     if (cm != MoveNumberOne)
9615         LoadGameOneMove(cm);
9616
9617     /* load the remaining moves from the file */
9618     while (LoadGameOneMove((ChessMove)0)) {
9619       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9620       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9621     }
9622
9623     /* rewind to the start of the game */
9624     currentMove = backwardMostMove;
9625
9626     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9627
9628     if (oldGameMode == AnalyzeFile ||
9629         oldGameMode == AnalyzeMode) {
9630       AnalyzeFileEvent();
9631     }
9632
9633     if (matchMode || appData.timeDelay == 0) {
9634       ToEndEvent();
9635       gameMode = EditGame;
9636       ModeHighlight();
9637     } else if (appData.timeDelay > 0) {
9638       AutoPlayGameLoop();
9639     }
9640
9641     if (appData.debugMode) 
9642         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9643
9644     loadFlag = 0; /* [HGM] true game starts */
9645     return TRUE;
9646 }
9647
9648 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9649 int
9650 ReloadPosition(offset)
9651      int offset;
9652 {
9653     int positionNumber = lastLoadPositionNumber + offset;
9654     if (lastLoadPositionFP == NULL) {
9655         DisplayError(_("No position has been loaded yet"), 0);
9656         return FALSE;
9657     }
9658     if (positionNumber <= 0) {
9659         DisplayError(_("Can't back up any further"), 0);
9660         return FALSE;
9661     }
9662     return LoadPosition(lastLoadPositionFP, positionNumber,
9663                         lastLoadPositionTitle);
9664 }
9665
9666 /* Load the nth position from the given file */
9667 int
9668 LoadPositionFromFile(filename, n, title)
9669      char *filename;
9670      int n;
9671      char *title;
9672 {
9673     FILE *f;
9674     char buf[MSG_SIZ];
9675
9676     if (strcmp(filename, "-") == 0) {
9677         return LoadPosition(stdin, n, "stdin");
9678     } else {
9679         f = fopen(filename, "rb");
9680         if (f == NULL) {
9681             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9682             DisplayError(buf, errno);
9683             return FALSE;
9684         } else {
9685             return LoadPosition(f, n, title);
9686         }
9687     }
9688 }
9689
9690 /* Load the nth position from the given open file, and close it */
9691 int
9692 LoadPosition(f, positionNumber, title)
9693      FILE *f;
9694      int positionNumber;
9695      char *title;
9696 {
9697     char *p, line[MSG_SIZ];
9698     Board initial_position;
9699     int i, j, fenMode, pn;
9700     
9701     if (gameMode == Training )
9702         SetTrainingModeOff();
9703
9704     if (gameMode != BeginningOfGame) {
9705         Reset(FALSE, TRUE);
9706     }
9707     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9708         fclose(lastLoadPositionFP);
9709     }
9710     if (positionNumber == 0) positionNumber = 1;
9711     lastLoadPositionFP = f;
9712     lastLoadPositionNumber = positionNumber;
9713     strcpy(lastLoadPositionTitle, title);
9714     if (first.pr == NoProc) {
9715       StartChessProgram(&first);
9716       InitChessProgram(&first, FALSE);
9717     }    
9718     pn = positionNumber;
9719     if (positionNumber < 0) {
9720         /* Negative position number means to seek to that byte offset */
9721         if (fseek(f, -positionNumber, 0) == -1) {
9722             DisplayError(_("Can't seek on position file"), 0);
9723             return FALSE;
9724         };
9725         pn = 1;
9726     } else {
9727         if (fseek(f, 0, 0) == -1) {
9728             if (f == lastLoadPositionFP ?
9729                 positionNumber == lastLoadPositionNumber + 1 :
9730                 positionNumber == 1) {
9731                 pn = 1;
9732             } else {
9733                 DisplayError(_("Can't seek on position file"), 0);
9734                 return FALSE;
9735             }
9736         }
9737     }
9738     /* See if this file is FEN or old-style xboard */
9739     if (fgets(line, MSG_SIZ, f) == NULL) {
9740         DisplayError(_("Position not found in file"), 0);
9741         return FALSE;
9742     }
9743     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9744     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9745
9746     if (pn >= 2) {
9747         if (fenMode || line[0] == '#') pn--;
9748         while (pn > 0) {
9749             /* skip positions before number pn */
9750             if (fgets(line, MSG_SIZ, f) == NULL) {
9751                 Reset(TRUE, TRUE);
9752                 DisplayError(_("Position not found in file"), 0);
9753                 return FALSE;
9754             }
9755             if (fenMode || line[0] == '#') pn--;
9756         }
9757     }
9758
9759     if (fenMode) {
9760         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9761             DisplayError(_("Bad FEN position in file"), 0);
9762             return FALSE;
9763         }
9764     } else {
9765         (void) fgets(line, MSG_SIZ, f);
9766         (void) fgets(line, MSG_SIZ, f);
9767     
9768         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9769             (void) fgets(line, MSG_SIZ, f);
9770             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9771                 if (*p == ' ')
9772                   continue;
9773                 initial_position[i][j++] = CharToPiece(*p);
9774             }
9775         }
9776     
9777         blackPlaysFirst = FALSE;
9778         if (!feof(f)) {
9779             (void) fgets(line, MSG_SIZ, f);
9780             if (strncmp(line, "black", strlen("black"))==0)
9781               blackPlaysFirst = TRUE;
9782         }
9783     }
9784     startedFromSetupPosition = TRUE;
9785     
9786     SendToProgram("force\n", &first);
9787     CopyBoard(boards[0], initial_position);
9788     if (blackPlaysFirst) {
9789         currentMove = forwardMostMove = backwardMostMove = 1;
9790         strcpy(moveList[0], "");
9791         strcpy(parseList[0], "");
9792         CopyBoard(boards[1], initial_position);
9793         DisplayMessage("", _("Black to play"));
9794     } else {
9795         currentMove = forwardMostMove = backwardMostMove = 0;
9796         DisplayMessage("", _("White to play"));
9797     }
9798     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9799     SendBoard(&first, forwardMostMove);
9800     if (appData.debugMode) {
9801 int i, j;
9802   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9803   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9804         fprintf(debugFP, "Load Position\n");
9805     }
9806
9807     if (positionNumber > 1) {
9808         sprintf(line, "%s %d", title, positionNumber);
9809         DisplayTitle(line);
9810     } else {
9811         DisplayTitle(title);
9812     }
9813     gameMode = EditGame;
9814     ModeHighlight();
9815     ResetClocks();
9816     timeRemaining[0][1] = whiteTimeRemaining;
9817     timeRemaining[1][1] = blackTimeRemaining;
9818     DrawPosition(FALSE, boards[currentMove]);
9819    
9820     return TRUE;
9821 }
9822
9823
9824 void
9825 CopyPlayerNameIntoFileName(dest, src)
9826      char **dest, *src;
9827 {
9828     while (*src != NULLCHAR && *src != ',') {
9829         if (*src == ' ') {
9830             *(*dest)++ = '_';
9831             src++;
9832         } else {
9833             *(*dest)++ = *src++;
9834         }
9835     }
9836 }
9837
9838 char *DefaultFileName(ext)
9839      char *ext;
9840 {
9841     static char def[MSG_SIZ];
9842     char *p;
9843
9844     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9845         p = def;
9846         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9847         *p++ = '-';
9848         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9849         *p++ = '.';
9850         strcpy(p, ext);
9851     } else {
9852         def[0] = NULLCHAR;
9853     }
9854     return def;
9855 }
9856
9857 /* Save the current game to the given file */
9858 int
9859 SaveGameToFile(filename, append)
9860      char *filename;
9861      int append;
9862 {
9863     FILE *f;
9864     char buf[MSG_SIZ];
9865
9866     if (strcmp(filename, "-") == 0) {
9867         return SaveGame(stdout, 0, NULL);
9868     } else {
9869         f = fopen(filename, append ? "a" : "w");
9870         if (f == NULL) {
9871             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9872             DisplayError(buf, errno);
9873             return FALSE;
9874         } else {
9875             return SaveGame(f, 0, NULL);
9876         }
9877     }
9878 }
9879
9880 char *
9881 SavePart(str)
9882      char *str;
9883 {
9884     static char buf[MSG_SIZ];
9885     char *p;
9886     
9887     p = strchr(str, ' ');
9888     if (p == NULL) return str;
9889     strncpy(buf, str, p - str);
9890     buf[p - str] = NULLCHAR;
9891     return buf;
9892 }
9893
9894 #define PGN_MAX_LINE 75
9895
9896 #define PGN_SIDE_WHITE  0
9897 #define PGN_SIDE_BLACK  1
9898
9899 /* [AS] */
9900 static int FindFirstMoveOutOfBook( int side )
9901 {
9902     int result = -1;
9903
9904     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9905         int index = backwardMostMove;
9906         int has_book_hit = 0;
9907
9908         if( (index % 2) != side ) {
9909             index++;
9910         }
9911
9912         while( index < forwardMostMove ) {
9913             /* Check to see if engine is in book */
9914             int depth = pvInfoList[index].depth;
9915             int score = pvInfoList[index].score;
9916             int in_book = 0;
9917
9918             if( depth <= 2 ) {
9919                 in_book = 1;
9920             }
9921             else if( score == 0 && depth == 63 ) {
9922                 in_book = 1; /* Zappa */
9923             }
9924             else if( score == 2 && depth == 99 ) {
9925                 in_book = 1; /* Abrok */
9926             }
9927
9928             has_book_hit += in_book;
9929
9930             if( ! in_book ) {
9931                 result = index;
9932
9933                 break;
9934             }
9935
9936             index += 2;
9937         }
9938     }
9939
9940     return result;
9941 }
9942
9943 /* [AS] */
9944 void GetOutOfBookInfo( char * buf )
9945 {
9946     int oob[2];
9947     int i;
9948     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9949
9950     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9951     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9952
9953     *buf = '\0';
9954
9955     if( oob[0] >= 0 || oob[1] >= 0 ) {
9956         for( i=0; i<2; i++ ) {
9957             int idx = oob[i];
9958
9959             if( idx >= 0 ) {
9960                 if( i > 0 && oob[0] >= 0 ) {
9961                     strcat( buf, "   " );
9962                 }
9963
9964                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9965                 sprintf( buf+strlen(buf), "%s%.2f", 
9966                     pvInfoList[idx].score >= 0 ? "+" : "",
9967                     pvInfoList[idx].score / 100.0 );
9968             }
9969         }
9970     }
9971 }
9972
9973 /* Save game in PGN style and close the file */
9974 int
9975 SaveGamePGN(f)
9976      FILE *f;
9977 {
9978     int i, offset, linelen, newblock;
9979     time_t tm;
9980 //    char *movetext;
9981     char numtext[32];
9982     int movelen, numlen, blank;
9983     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9984
9985     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9986     
9987     tm = time((time_t *) NULL);
9988     
9989     PrintPGNTags(f, &gameInfo);
9990     
9991     if (backwardMostMove > 0 || startedFromSetupPosition) {
9992         char *fen = PositionToFEN(backwardMostMove, NULL);
9993         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9994         fprintf(f, "\n{--------------\n");
9995         PrintPosition(f, backwardMostMove);
9996         fprintf(f, "--------------}\n");
9997         free(fen);
9998     }
9999     else {
10000         /* [AS] Out of book annotation */
10001         if( appData.saveOutOfBookInfo ) {
10002             char buf[64];
10003
10004             GetOutOfBookInfo( buf );
10005
10006             if( buf[0] != '\0' ) {
10007                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10008             }
10009         }
10010
10011         fprintf(f, "\n");
10012     }
10013
10014     i = backwardMostMove;
10015     linelen = 0;
10016     newblock = TRUE;
10017
10018     while (i < forwardMostMove) {
10019         /* Print comments preceding this move */
10020         if (commentList[i] != NULL) {
10021             if (linelen > 0) fprintf(f, "\n");
10022             fprintf(f, "%s", commentList[i]);
10023             linelen = 0;
10024             newblock = TRUE;
10025         }
10026
10027         /* Format move number */
10028         if ((i % 2) == 0) {
10029             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10030         } else {
10031             if (newblock) {
10032                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10033             } else {
10034                 numtext[0] = NULLCHAR;
10035             }
10036         }
10037         numlen = strlen(numtext);
10038         newblock = FALSE;
10039
10040         /* Print move number */
10041         blank = linelen > 0 && numlen > 0;
10042         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10043             fprintf(f, "\n");
10044             linelen = 0;
10045             blank = 0;
10046         }
10047         if (blank) {
10048             fprintf(f, " ");
10049             linelen++;
10050         }
10051         fprintf(f, "%s", numtext);
10052         linelen += numlen;
10053
10054         /* Get move */
10055         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10056         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10057
10058         /* Print move */
10059         blank = linelen > 0 && movelen > 0;
10060         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10061             fprintf(f, "\n");
10062             linelen = 0;
10063             blank = 0;
10064         }
10065         if (blank) {
10066             fprintf(f, " ");
10067             linelen++;
10068         }
10069         fprintf(f, "%s", move_buffer);
10070         linelen += movelen;
10071
10072         /* [AS] Add PV info if present */
10073         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10074             /* [HGM] add time */
10075             char buf[MSG_SIZ]; int seconds;
10076
10077             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10078
10079             if( seconds <= 0) buf[0] = 0; else
10080             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10081                 seconds = (seconds + 4)/10; // round to full seconds
10082                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10083                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10084             }
10085
10086             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10087                 pvInfoList[i].score >= 0 ? "+" : "",
10088                 pvInfoList[i].score / 100.0,
10089                 pvInfoList[i].depth,
10090                 buf );
10091
10092             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10093
10094             /* Print score/depth */
10095             blank = linelen > 0 && movelen > 0;
10096             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10097                 fprintf(f, "\n");
10098                 linelen = 0;
10099                 blank = 0;
10100             }
10101             if (blank) {
10102                 fprintf(f, " ");
10103                 linelen++;
10104             }
10105             fprintf(f, "%s", move_buffer);
10106             linelen += movelen;
10107         }
10108
10109         i++;
10110     }
10111     
10112     /* Start a new line */
10113     if (linelen > 0) fprintf(f, "\n");
10114
10115     /* Print comments after last move */
10116     if (commentList[i] != NULL) {
10117         fprintf(f, "%s\n", commentList[i]);
10118     }
10119
10120     /* Print result */
10121     if (gameInfo.resultDetails != NULL &&
10122         gameInfo.resultDetails[0] != NULLCHAR) {
10123         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10124                 PGNResult(gameInfo.result));
10125     } else {
10126         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10127     }
10128
10129     fclose(f);
10130     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10131     return TRUE;
10132 }
10133
10134 /* Save game in old style and close the file */
10135 int
10136 SaveGameOldStyle(f)
10137      FILE *f;
10138 {
10139     int i, offset;
10140     time_t tm;
10141     
10142     tm = time((time_t *) NULL);
10143     
10144     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10145     PrintOpponents(f);
10146     
10147     if (backwardMostMove > 0 || startedFromSetupPosition) {
10148         fprintf(f, "\n[--------------\n");
10149         PrintPosition(f, backwardMostMove);
10150         fprintf(f, "--------------]\n");
10151     } else {
10152         fprintf(f, "\n");
10153     }
10154
10155     i = backwardMostMove;
10156     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10157
10158     while (i < forwardMostMove) {
10159         if (commentList[i] != NULL) {
10160             fprintf(f, "[%s]\n", commentList[i]);
10161         }
10162
10163         if ((i % 2) == 1) {
10164             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10165             i++;
10166         } else {
10167             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10168             i++;
10169             if (commentList[i] != NULL) {
10170                 fprintf(f, "\n");
10171                 continue;
10172             }
10173             if (i >= forwardMostMove) {
10174                 fprintf(f, "\n");
10175                 break;
10176             }
10177             fprintf(f, "%s\n", parseList[i]);
10178             i++;
10179         }
10180     }
10181     
10182     if (commentList[i] != NULL) {
10183         fprintf(f, "[%s]\n", commentList[i]);
10184     }
10185
10186     /* This isn't really the old style, but it's close enough */
10187     if (gameInfo.resultDetails != NULL &&
10188         gameInfo.resultDetails[0] != NULLCHAR) {
10189         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10190                 gameInfo.resultDetails);
10191     } else {
10192         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10193     }
10194
10195     fclose(f);
10196     return TRUE;
10197 }
10198
10199 /* Save the current game to open file f and close the file */
10200 int
10201 SaveGame(f, dummy, dummy2)
10202      FILE *f;
10203      int dummy;
10204      char *dummy2;
10205 {
10206     if (gameMode == EditPosition) EditPositionDone(TRUE);
10207     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10208     if (appData.oldSaveStyle)
10209       return SaveGameOldStyle(f);
10210     else
10211       return SaveGamePGN(f);
10212 }
10213
10214 /* Save the current position to the given file */
10215 int
10216 SavePositionToFile(filename)
10217      char *filename;
10218 {
10219     FILE *f;
10220     char buf[MSG_SIZ];
10221
10222     if (strcmp(filename, "-") == 0) {
10223         return SavePosition(stdout, 0, NULL);
10224     } else {
10225         f = fopen(filename, "a");
10226         if (f == NULL) {
10227             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10228             DisplayError(buf, errno);
10229             return FALSE;
10230         } else {
10231             SavePosition(f, 0, NULL);
10232             return TRUE;
10233         }
10234     }
10235 }
10236
10237 /* Save the current position to the given open file and close the file */
10238 int
10239 SavePosition(f, dummy, dummy2)
10240      FILE *f;
10241      int dummy;
10242      char *dummy2;
10243 {
10244     time_t tm;
10245     char *fen;
10246     
10247     if (gameMode == EditPosition) EditPositionDone(TRUE);
10248     if (appData.oldSaveStyle) {
10249         tm = time((time_t *) NULL);
10250     
10251         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10252         PrintOpponents(f);
10253         fprintf(f, "[--------------\n");
10254         PrintPosition(f, currentMove);
10255         fprintf(f, "--------------]\n");
10256     } else {
10257         fen = PositionToFEN(currentMove, NULL);
10258         fprintf(f, "%s\n", fen);
10259         free(fen);
10260     }
10261     fclose(f);
10262     return TRUE;
10263 }
10264
10265 void
10266 ReloadCmailMsgEvent(unregister)
10267      int unregister;
10268 {
10269 #if !WIN32
10270     static char *inFilename = NULL;
10271     static char *outFilename;
10272     int i;
10273     struct stat inbuf, outbuf;
10274     int status;
10275     
10276     /* Any registered moves are unregistered if unregister is set, */
10277     /* i.e. invoked by the signal handler */
10278     if (unregister) {
10279         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10280             cmailMoveRegistered[i] = FALSE;
10281             if (cmailCommentList[i] != NULL) {
10282                 free(cmailCommentList[i]);
10283                 cmailCommentList[i] = NULL;
10284             }
10285         }
10286         nCmailMovesRegistered = 0;
10287     }
10288
10289     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10290         cmailResult[i] = CMAIL_NOT_RESULT;
10291     }
10292     nCmailResults = 0;
10293
10294     if (inFilename == NULL) {
10295         /* Because the filenames are static they only get malloced once  */
10296         /* and they never get freed                                      */
10297         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10298         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10299
10300         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10301         sprintf(outFilename, "%s.out", appData.cmailGameName);
10302     }
10303     
10304     status = stat(outFilename, &outbuf);
10305     if (status < 0) {
10306         cmailMailedMove = FALSE;
10307     } else {
10308         status = stat(inFilename, &inbuf);
10309         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10310     }
10311     
10312     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10313        counts the games, notes how each one terminated, etc.
10314        
10315        It would be nice to remove this kludge and instead gather all
10316        the information while building the game list.  (And to keep it
10317        in the game list nodes instead of having a bunch of fixed-size
10318        parallel arrays.)  Note this will require getting each game's
10319        termination from the PGN tags, as the game list builder does
10320        not process the game moves.  --mann
10321        */
10322     cmailMsgLoaded = TRUE;
10323     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10324     
10325     /* Load first game in the file or popup game menu */
10326     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10327
10328 #endif /* !WIN32 */
10329     return;
10330 }
10331
10332 int
10333 RegisterMove()
10334 {
10335     FILE *f;
10336     char string[MSG_SIZ];
10337
10338     if (   cmailMailedMove
10339         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10340         return TRUE;            /* Allow free viewing  */
10341     }
10342
10343     /* Unregister move to ensure that we don't leave RegisterMove        */
10344     /* with the move registered when the conditions for registering no   */
10345     /* longer hold                                                       */
10346     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10347         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10348         nCmailMovesRegistered --;
10349
10350         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10351           {
10352               free(cmailCommentList[lastLoadGameNumber - 1]);
10353               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10354           }
10355     }
10356
10357     if (cmailOldMove == -1) {
10358         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10359         return FALSE;
10360     }
10361
10362     if (currentMove > cmailOldMove + 1) {
10363         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10364         return FALSE;
10365     }
10366
10367     if (currentMove < cmailOldMove) {
10368         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10369         return FALSE;
10370     }
10371
10372     if (forwardMostMove > currentMove) {
10373         /* Silently truncate extra moves */
10374         TruncateGame();
10375     }
10376
10377     if (   (currentMove == cmailOldMove + 1)
10378         || (   (currentMove == cmailOldMove)
10379             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10380                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10381         if (gameInfo.result != GameUnfinished) {
10382             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10383         }
10384
10385         if (commentList[currentMove] != NULL) {
10386             cmailCommentList[lastLoadGameNumber - 1]
10387               = StrSave(commentList[currentMove]);
10388         }
10389         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10390
10391         if (appData.debugMode)
10392           fprintf(debugFP, "Saving %s for game %d\n",
10393                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10394
10395         sprintf(string,
10396                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10397         
10398         f = fopen(string, "w");
10399         if (appData.oldSaveStyle) {
10400             SaveGameOldStyle(f); /* also closes the file */
10401             
10402             sprintf(string, "%s.pos.out", appData.cmailGameName);
10403             f = fopen(string, "w");
10404             SavePosition(f, 0, NULL); /* also closes the file */
10405         } else {
10406             fprintf(f, "{--------------\n");
10407             PrintPosition(f, currentMove);
10408             fprintf(f, "--------------}\n\n");
10409             
10410             SaveGame(f, 0, NULL); /* also closes the file*/
10411         }
10412         
10413         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10414         nCmailMovesRegistered ++;
10415     } else if (nCmailGames == 1) {
10416         DisplayError(_("You have not made a move yet"), 0);
10417         return FALSE;
10418     }
10419
10420     return TRUE;
10421 }
10422
10423 void
10424 MailMoveEvent()
10425 {
10426 #if !WIN32
10427     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10428     FILE *commandOutput;
10429     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10430     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10431     int nBuffers;
10432     int i;
10433     int archived;
10434     char *arcDir;
10435
10436     if (! cmailMsgLoaded) {
10437         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10438         return;
10439     }
10440
10441     if (nCmailGames == nCmailResults) {
10442         DisplayError(_("No unfinished games"), 0);
10443         return;
10444     }
10445
10446 #if CMAIL_PROHIBIT_REMAIL
10447     if (cmailMailedMove) {
10448         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);
10449         DisplayError(msg, 0);
10450         return;
10451     }
10452 #endif
10453
10454     if (! (cmailMailedMove || RegisterMove())) return;
10455     
10456     if (   cmailMailedMove
10457         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10458         sprintf(string, partCommandString,
10459                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10460         commandOutput = popen(string, "r");
10461
10462         if (commandOutput == NULL) {
10463             DisplayError(_("Failed to invoke cmail"), 0);
10464         } else {
10465             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10466                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10467             }
10468             if (nBuffers > 1) {
10469                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10470                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10471                 nBytes = MSG_SIZ - 1;
10472             } else {
10473                 (void) memcpy(msg, buffer, nBytes);
10474             }
10475             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10476
10477             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10478                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10479
10480                 archived = TRUE;
10481                 for (i = 0; i < nCmailGames; i ++) {
10482                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10483                         archived = FALSE;
10484                     }
10485                 }
10486                 if (   archived
10487                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10488                         != NULL)) {
10489                     sprintf(buffer, "%s/%s.%s.archive",
10490                             arcDir,
10491                             appData.cmailGameName,
10492                             gameInfo.date);
10493                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10494                     cmailMsgLoaded = FALSE;
10495                 }
10496             }
10497
10498             DisplayInformation(msg);
10499             pclose(commandOutput);
10500         }
10501     } else {
10502         if ((*cmailMsg) != '\0') {
10503             DisplayInformation(cmailMsg);
10504         }
10505     }
10506
10507     return;
10508 #endif /* !WIN32 */
10509 }
10510
10511 char *
10512 CmailMsg()
10513 {
10514 #if WIN32
10515     return NULL;
10516 #else
10517     int  prependComma = 0;
10518     char number[5];
10519     char string[MSG_SIZ];       /* Space for game-list */
10520     int  i;
10521     
10522     if (!cmailMsgLoaded) return "";
10523
10524     if (cmailMailedMove) {
10525         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10526     } else {
10527         /* Create a list of games left */
10528         sprintf(string, "[");
10529         for (i = 0; i < nCmailGames; i ++) {
10530             if (! (   cmailMoveRegistered[i]
10531                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10532                 if (prependComma) {
10533                     sprintf(number, ",%d", i + 1);
10534                 } else {
10535                     sprintf(number, "%d", i + 1);
10536                     prependComma = 1;
10537                 }
10538                 
10539                 strcat(string, number);
10540             }
10541         }
10542         strcat(string, "]");
10543
10544         if (nCmailMovesRegistered + nCmailResults == 0) {
10545             switch (nCmailGames) {
10546               case 1:
10547                 sprintf(cmailMsg,
10548                         _("Still need to make move for game\n"));
10549                 break;
10550                 
10551               case 2:
10552                 sprintf(cmailMsg,
10553                         _("Still need to make moves for both games\n"));
10554                 break;
10555                 
10556               default:
10557                 sprintf(cmailMsg,
10558                         _("Still need to make moves for all %d games\n"),
10559                         nCmailGames);
10560                 break;
10561             }
10562         } else {
10563             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10564               case 1:
10565                 sprintf(cmailMsg,
10566                         _("Still need to make a move for game %s\n"),
10567                         string);
10568                 break;
10569                 
10570               case 0:
10571                 if (nCmailResults == nCmailGames) {
10572                     sprintf(cmailMsg, _("No unfinished games\n"));
10573                 } else {
10574                     sprintf(cmailMsg, _("Ready to send mail\n"));
10575                 }
10576                 break;
10577                 
10578               default:
10579                 sprintf(cmailMsg,
10580                         _("Still need to make moves for games %s\n"),
10581                         string);
10582             }
10583         }
10584     }
10585     return cmailMsg;
10586 #endif /* WIN32 */
10587 }
10588
10589 void
10590 ResetGameEvent()
10591 {
10592     if (gameMode == Training)
10593       SetTrainingModeOff();
10594
10595     Reset(TRUE, TRUE);
10596     cmailMsgLoaded = FALSE;
10597     if (appData.icsActive) {
10598       SendToICS(ics_prefix);
10599       SendToICS("refresh\n");
10600     }
10601 }
10602
10603 void
10604 ExitEvent(status)
10605      int status;
10606 {
10607     exiting++;
10608     if (exiting > 2) {
10609       /* Give up on clean exit */
10610       exit(status);
10611     }
10612     if (exiting > 1) {
10613       /* Keep trying for clean exit */
10614       return;
10615     }
10616
10617     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10618
10619     if (telnetISR != NULL) {
10620       RemoveInputSource(telnetISR);
10621     }
10622     if (icsPR != NoProc) {
10623       DestroyChildProcess(icsPR, TRUE);
10624     }
10625
10626     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10627     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10628
10629     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10630     /* make sure this other one finishes before killing it!                  */
10631     if(endingGame) { int count = 0;
10632         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10633         while(endingGame && count++ < 10) DoSleep(1);
10634         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10635     }
10636
10637     /* Kill off chess programs */
10638     if (first.pr != NoProc) {
10639         ExitAnalyzeMode();
10640         
10641         DoSleep( appData.delayBeforeQuit );
10642         SendToProgram("quit\n", &first);
10643         DoSleep( appData.delayAfterQuit );
10644         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10645     }
10646     if (second.pr != NoProc) {
10647         DoSleep( appData.delayBeforeQuit );
10648         SendToProgram("quit\n", &second);
10649         DoSleep( appData.delayAfterQuit );
10650         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10651     }
10652     if (first.isr != NULL) {
10653         RemoveInputSource(first.isr);
10654     }
10655     if (second.isr != NULL) {
10656         RemoveInputSource(second.isr);
10657     }
10658
10659     ShutDownFrontEnd();
10660     exit(status);
10661 }
10662
10663 void
10664 PauseEvent()
10665 {
10666     if (appData.debugMode)
10667         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10668     if (pausing) {
10669         pausing = FALSE;
10670         ModeHighlight();
10671         if (gameMode == MachinePlaysWhite ||
10672             gameMode == MachinePlaysBlack) {
10673             StartClocks();
10674         } else {
10675             DisplayBothClocks();
10676         }
10677         if (gameMode == PlayFromGameFile) {
10678             if (appData.timeDelay >= 0) 
10679                 AutoPlayGameLoop();
10680         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10681             Reset(FALSE, TRUE);
10682             SendToICS(ics_prefix);
10683             SendToICS("refresh\n");
10684         } else if (currentMove < forwardMostMove) {
10685             ForwardInner(forwardMostMove);
10686         }
10687         pauseExamInvalid = FALSE;
10688     } else {
10689         switch (gameMode) {
10690           default:
10691             return;
10692           case IcsExamining:
10693             pauseExamForwardMostMove = forwardMostMove;
10694             pauseExamInvalid = FALSE;
10695             /* fall through */
10696           case IcsObserving:
10697           case IcsPlayingWhite:
10698           case IcsPlayingBlack:
10699             pausing = TRUE;
10700             ModeHighlight();
10701             return;
10702           case PlayFromGameFile:
10703             (void) StopLoadGameTimer();
10704             pausing = TRUE;
10705             ModeHighlight();
10706             break;
10707           case BeginningOfGame:
10708             if (appData.icsActive) return;
10709             /* else fall through */
10710           case MachinePlaysWhite:
10711           case MachinePlaysBlack:
10712           case TwoMachinesPlay:
10713             if (forwardMostMove == 0)
10714               return;           /* don't pause if no one has moved */
10715             if ((gameMode == MachinePlaysWhite &&
10716                  !WhiteOnMove(forwardMostMove)) ||
10717                 (gameMode == MachinePlaysBlack &&
10718                  WhiteOnMove(forwardMostMove))) {
10719                 StopClocks();
10720             }
10721             pausing = TRUE;
10722             ModeHighlight();
10723             break;
10724         }
10725     }
10726 }
10727
10728 void
10729 EditCommentEvent()
10730 {
10731     char title[MSG_SIZ];
10732
10733     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10734         strcpy(title, _("Edit comment"));
10735     } else {
10736         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10737                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10738                 parseList[currentMove - 1]);
10739     }
10740
10741     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10742 }
10743
10744
10745 void
10746 EditTagsEvent()
10747 {
10748     char *tags = PGNTags(&gameInfo);
10749     EditTagsPopUp(tags);
10750     free(tags);
10751 }
10752
10753 void
10754 AnalyzeModeEvent()
10755 {
10756     if (appData.noChessProgram || gameMode == AnalyzeMode)
10757       return;
10758
10759     if (gameMode != AnalyzeFile) {
10760         if (!appData.icsEngineAnalyze) {
10761                EditGameEvent();
10762                if (gameMode != EditGame) return;
10763         }
10764         ResurrectChessProgram();
10765         SendToProgram("analyze\n", &first);
10766         first.analyzing = TRUE;
10767         /*first.maybeThinking = TRUE;*/
10768         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10769         EngineOutputPopUp();
10770     }
10771     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10772     pausing = FALSE;
10773     ModeHighlight();
10774     SetGameInfo();
10775
10776     StartAnalysisClock();
10777     GetTimeMark(&lastNodeCountTime);
10778     lastNodeCount = 0;
10779 }
10780
10781 void
10782 AnalyzeFileEvent()
10783 {
10784     if (appData.noChessProgram || gameMode == AnalyzeFile)
10785       return;
10786
10787     if (gameMode != AnalyzeMode) {
10788         EditGameEvent();
10789         if (gameMode != EditGame) return;
10790         ResurrectChessProgram();
10791         SendToProgram("analyze\n", &first);
10792         first.analyzing = TRUE;
10793         /*first.maybeThinking = TRUE;*/
10794         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10795         EngineOutputPopUp();
10796     }
10797     gameMode = AnalyzeFile;
10798     pausing = FALSE;
10799     ModeHighlight();
10800     SetGameInfo();
10801
10802     StartAnalysisClock();
10803     GetTimeMark(&lastNodeCountTime);
10804     lastNodeCount = 0;
10805 }
10806
10807 void
10808 MachineWhiteEvent()
10809 {
10810     char buf[MSG_SIZ];
10811     char *bookHit = NULL;
10812
10813     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10814       return;
10815
10816
10817     if (gameMode == PlayFromGameFile || 
10818         gameMode == TwoMachinesPlay  || 
10819         gameMode == Training         || 
10820         gameMode == AnalyzeMode      || 
10821         gameMode == EndOfGame)
10822         EditGameEvent();
10823
10824     if (gameMode == EditPosition) 
10825         EditPositionDone(TRUE);
10826
10827     if (!WhiteOnMove(currentMove)) {
10828         DisplayError(_("It is not White's turn"), 0);
10829         return;
10830     }
10831   
10832     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10833       ExitAnalyzeMode();
10834
10835     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10836         gameMode == AnalyzeFile)
10837         TruncateGame();
10838
10839     ResurrectChessProgram();    /* in case it isn't running */
10840     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10841         gameMode = MachinePlaysWhite;
10842         ResetClocks();
10843     } else
10844     gameMode = MachinePlaysWhite;
10845     pausing = FALSE;
10846     ModeHighlight();
10847     SetGameInfo();
10848     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10849     DisplayTitle(buf);
10850     if (first.sendName) {
10851       sprintf(buf, "name %s\n", gameInfo.black);
10852       SendToProgram(buf, &first);
10853     }
10854     if (first.sendTime) {
10855       if (first.useColors) {
10856         SendToProgram("black\n", &first); /*gnu kludge*/
10857       }
10858       SendTimeRemaining(&first, TRUE);
10859     }
10860     if (first.useColors) {
10861       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10862     }
10863     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10864     SetMachineThinkingEnables();
10865     first.maybeThinking = TRUE;
10866     StartClocks();
10867     firstMove = FALSE;
10868
10869     if (appData.autoFlipView && !flipView) {
10870       flipView = !flipView;
10871       DrawPosition(FALSE, NULL);
10872       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10873     }
10874
10875     if(bookHit) { // [HGM] book: simulate book reply
10876         static char bookMove[MSG_SIZ]; // a bit generous?
10877
10878         programStats.nodes = programStats.depth = programStats.time = 
10879         programStats.score = programStats.got_only_move = 0;
10880         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10881
10882         strcpy(bookMove, "move ");
10883         strcat(bookMove, bookHit);
10884         HandleMachineMove(bookMove, &first);
10885     }
10886 }
10887
10888 void
10889 MachineBlackEvent()
10890 {
10891     char buf[MSG_SIZ];
10892    char *bookHit = NULL;
10893
10894     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10895         return;
10896
10897
10898     if (gameMode == PlayFromGameFile || 
10899         gameMode == TwoMachinesPlay  || 
10900         gameMode == Training         || 
10901         gameMode == AnalyzeMode      || 
10902         gameMode == EndOfGame)
10903         EditGameEvent();
10904
10905     if (gameMode == EditPosition) 
10906         EditPositionDone(TRUE);
10907
10908     if (WhiteOnMove(currentMove)) {
10909         DisplayError(_("It is not Black's turn"), 0);
10910         return;
10911     }
10912     
10913     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10914       ExitAnalyzeMode();
10915
10916     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10917         gameMode == AnalyzeFile)
10918         TruncateGame();
10919
10920     ResurrectChessProgram();    /* in case it isn't running */
10921     gameMode = MachinePlaysBlack;
10922     pausing = FALSE;
10923     ModeHighlight();
10924     SetGameInfo();
10925     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10926     DisplayTitle(buf);
10927     if (first.sendName) {
10928       sprintf(buf, "name %s\n", gameInfo.white);
10929       SendToProgram(buf, &first);
10930     }
10931     if (first.sendTime) {
10932       if (first.useColors) {
10933         SendToProgram("white\n", &first); /*gnu kludge*/
10934       }
10935       SendTimeRemaining(&first, FALSE);
10936     }
10937     if (first.useColors) {
10938       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10939     }
10940     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10941     SetMachineThinkingEnables();
10942     first.maybeThinking = TRUE;
10943     StartClocks();
10944
10945     if (appData.autoFlipView && flipView) {
10946       flipView = !flipView;
10947       DrawPosition(FALSE, NULL);
10948       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10949     }
10950     if(bookHit) { // [HGM] book: simulate book reply
10951         static char bookMove[MSG_SIZ]; // a bit generous?
10952
10953         programStats.nodes = programStats.depth = programStats.time = 
10954         programStats.score = programStats.got_only_move = 0;
10955         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10956
10957         strcpy(bookMove, "move ");
10958         strcat(bookMove, bookHit);
10959         HandleMachineMove(bookMove, &first);
10960     }
10961 }
10962
10963
10964 void
10965 DisplayTwoMachinesTitle()
10966 {
10967     char buf[MSG_SIZ];
10968     if (appData.matchGames > 0) {
10969         if (first.twoMachinesColor[0] == 'w') {
10970             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10971                     gameInfo.white, gameInfo.black,
10972                     first.matchWins, second.matchWins,
10973                     matchGame - 1 - (first.matchWins + second.matchWins));
10974         } else {
10975             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10976                     gameInfo.white, gameInfo.black,
10977                     second.matchWins, first.matchWins,
10978                     matchGame - 1 - (first.matchWins + second.matchWins));
10979         }
10980     } else {
10981         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10982     }
10983     DisplayTitle(buf);
10984 }
10985
10986 void
10987 TwoMachinesEvent P((void))
10988 {
10989     int i;
10990     char buf[MSG_SIZ];
10991     ChessProgramState *onmove;
10992     char *bookHit = NULL;
10993     
10994     if (appData.noChessProgram) return;
10995
10996     switch (gameMode) {
10997       case TwoMachinesPlay:
10998         return;
10999       case MachinePlaysWhite:
11000       case MachinePlaysBlack:
11001         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11002             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11003             return;
11004         }
11005         /* fall through */
11006       case BeginningOfGame:
11007       case PlayFromGameFile:
11008       case EndOfGame:
11009         EditGameEvent();
11010         if (gameMode != EditGame) return;
11011         break;
11012       case EditPosition:
11013         EditPositionDone(TRUE);
11014         break;
11015       case AnalyzeMode:
11016       case AnalyzeFile:
11017         ExitAnalyzeMode();
11018         break;
11019       case EditGame:
11020       default:
11021         break;
11022     }
11023
11024 //    forwardMostMove = currentMove;
11025     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11026     ResurrectChessProgram();    /* in case first program isn't running */
11027
11028     if (second.pr == NULL) {
11029         StartChessProgram(&second);
11030         if (second.protocolVersion == 1) {
11031           TwoMachinesEventIfReady();
11032         } else {
11033           /* kludge: allow timeout for initial "feature" command */
11034           FreezeUI();
11035           DisplayMessage("", _("Starting second chess program"));
11036           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11037         }
11038         return;
11039     }
11040     DisplayMessage("", "");
11041     InitChessProgram(&second, FALSE);
11042     SendToProgram("force\n", &second);
11043     if (startedFromSetupPosition) {
11044         SendBoard(&second, backwardMostMove);
11045     if (appData.debugMode) {
11046         fprintf(debugFP, "Two Machines\n");
11047     }
11048     }
11049     for (i = backwardMostMove; i < forwardMostMove; i++) {
11050         SendMoveToProgram(i, &second);
11051     }
11052
11053     gameMode = TwoMachinesPlay;
11054     pausing = FALSE;
11055     ModeHighlight();
11056     SetGameInfo();
11057     DisplayTwoMachinesTitle();
11058     firstMove = TRUE;
11059     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11060         onmove = &first;
11061     } else {
11062         onmove = &second;
11063     }
11064
11065     SendToProgram(first.computerString, &first);
11066     if (first.sendName) {
11067       sprintf(buf, "name %s\n", second.tidy);
11068       SendToProgram(buf, &first);
11069     }
11070     SendToProgram(second.computerString, &second);
11071     if (second.sendName) {
11072       sprintf(buf, "name %s\n", first.tidy);
11073       SendToProgram(buf, &second);
11074     }
11075
11076     ResetClocks();
11077     if (!first.sendTime || !second.sendTime) {
11078         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11079         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11080     }
11081     if (onmove->sendTime) {
11082       if (onmove->useColors) {
11083         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11084       }
11085       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11086     }
11087     if (onmove->useColors) {
11088       SendToProgram(onmove->twoMachinesColor, onmove);
11089     }
11090     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11091 //    SendToProgram("go\n", onmove);
11092     onmove->maybeThinking = TRUE;
11093     SetMachineThinkingEnables();
11094
11095     StartClocks();
11096
11097     if(bookHit) { // [HGM] book: simulate book reply
11098         static char bookMove[MSG_SIZ]; // a bit generous?
11099
11100         programStats.nodes = programStats.depth = programStats.time = 
11101         programStats.score = programStats.got_only_move = 0;
11102         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11103
11104         strcpy(bookMove, "move ");
11105         strcat(bookMove, bookHit);
11106         savedMessage = bookMove; // args for deferred call
11107         savedState = onmove;
11108         ScheduleDelayedEvent(DeferredBookMove, 1);
11109     }
11110 }
11111
11112 void
11113 TrainingEvent()
11114 {
11115     if (gameMode == Training) {
11116       SetTrainingModeOff();
11117       gameMode = PlayFromGameFile;
11118       DisplayMessage("", _("Training mode off"));
11119     } else {
11120       gameMode = Training;
11121       animateTraining = appData.animate;
11122
11123       /* make sure we are not already at the end of the game */
11124       if (currentMove < forwardMostMove) {
11125         SetTrainingModeOn();
11126         DisplayMessage("", _("Training mode on"));
11127       } else {
11128         gameMode = PlayFromGameFile;
11129         DisplayError(_("Already at end of game"), 0);
11130       }
11131     }
11132     ModeHighlight();
11133 }
11134
11135 void
11136 IcsClientEvent()
11137 {
11138     if (!appData.icsActive) return;
11139     switch (gameMode) {
11140       case IcsPlayingWhite:
11141       case IcsPlayingBlack:
11142       case IcsObserving:
11143       case IcsIdle:
11144       case BeginningOfGame:
11145       case IcsExamining:
11146         return;
11147
11148       case EditGame:
11149         break;
11150
11151       case EditPosition:
11152         EditPositionDone(TRUE);
11153         break;
11154
11155       case AnalyzeMode:
11156       case AnalyzeFile:
11157         ExitAnalyzeMode();
11158         break;
11159         
11160       default:
11161         EditGameEvent();
11162         break;
11163     }
11164
11165     gameMode = IcsIdle;
11166     ModeHighlight();
11167     return;
11168 }
11169
11170
11171 void
11172 EditGameEvent()
11173 {
11174     int i;
11175
11176     switch (gameMode) {
11177       case Training:
11178         SetTrainingModeOff();
11179         break;
11180       case MachinePlaysWhite:
11181       case MachinePlaysBlack:
11182       case BeginningOfGame:
11183         SendToProgram("force\n", &first);
11184         SetUserThinkingEnables();
11185         break;
11186       case PlayFromGameFile:
11187         (void) StopLoadGameTimer();
11188         if (gameFileFP != NULL) {
11189             gameFileFP = NULL;
11190         }
11191         break;
11192       case EditPosition:
11193         EditPositionDone(TRUE);
11194         break;
11195       case AnalyzeMode:
11196       case AnalyzeFile:
11197         ExitAnalyzeMode();
11198         SendToProgram("force\n", &first);
11199         break;
11200       case TwoMachinesPlay:
11201         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11202         ResurrectChessProgram();
11203         SetUserThinkingEnables();
11204         break;
11205       case EndOfGame:
11206         ResurrectChessProgram();
11207         break;
11208       case IcsPlayingBlack:
11209       case IcsPlayingWhite:
11210         DisplayError(_("Warning: You are still playing a game"), 0);
11211         break;
11212       case IcsObserving:
11213         DisplayError(_("Warning: You are still observing a game"), 0);
11214         break;
11215       case IcsExamining:
11216         DisplayError(_("Warning: You are still examining a game"), 0);
11217         break;
11218       case IcsIdle:
11219         break;
11220       case EditGame:
11221       default:
11222         return;
11223     }
11224     
11225     pausing = FALSE;
11226     StopClocks();
11227     first.offeredDraw = second.offeredDraw = 0;
11228
11229     if (gameMode == PlayFromGameFile) {
11230         whiteTimeRemaining = timeRemaining[0][currentMove];
11231         blackTimeRemaining = timeRemaining[1][currentMove];
11232         DisplayTitle("");
11233     }
11234
11235     if (gameMode == MachinePlaysWhite ||
11236         gameMode == MachinePlaysBlack ||
11237         gameMode == TwoMachinesPlay ||
11238         gameMode == EndOfGame) {
11239         i = forwardMostMove;
11240         while (i > currentMove) {
11241             SendToProgram("undo\n", &first);
11242             i--;
11243         }
11244         whiteTimeRemaining = timeRemaining[0][currentMove];
11245         blackTimeRemaining = timeRemaining[1][currentMove];
11246         DisplayBothClocks();
11247         if (whiteFlag || blackFlag) {
11248             whiteFlag = blackFlag = 0;
11249         }
11250         DisplayTitle("");
11251     }           
11252     
11253     gameMode = EditGame;
11254     ModeHighlight();
11255     SetGameInfo();
11256 }
11257
11258
11259 void
11260 EditPositionEvent()
11261 {
11262     if (gameMode == EditPosition) {
11263         EditGameEvent();
11264         return;
11265     }
11266     
11267     EditGameEvent();
11268     if (gameMode != EditGame) return;
11269     
11270     gameMode = EditPosition;
11271     ModeHighlight();
11272     SetGameInfo();
11273     if (currentMove > 0)
11274       CopyBoard(boards[0], boards[currentMove]);
11275     
11276     blackPlaysFirst = !WhiteOnMove(currentMove);
11277     ResetClocks();
11278     currentMove = forwardMostMove = backwardMostMove = 0;
11279     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11280     DisplayMove(-1);
11281 }
11282
11283 void
11284 ExitAnalyzeMode()
11285 {
11286     /* [DM] icsEngineAnalyze - possible call from other functions */
11287     if (appData.icsEngineAnalyze) {
11288         appData.icsEngineAnalyze = FALSE;
11289
11290         DisplayMessage("",_("Close ICS engine analyze..."));
11291     }
11292     if (first.analysisSupport && first.analyzing) {
11293       SendToProgram("exit\n", &first);
11294       first.analyzing = FALSE;
11295     }
11296     thinkOutput[0] = NULLCHAR;
11297 }
11298
11299 void
11300 EditPositionDone(Boolean fakeRights)
11301 {
11302     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11303
11304     startedFromSetupPosition = TRUE;
11305     InitChessProgram(&first, FALSE);
11306     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11307       boards[0][EP_STATUS] = EP_NONE;
11308       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11309     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11310         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11311         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11312       } else boards[0][CASTLING][2] = NoRights;
11313     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11314         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11315         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11316       } else boards[0][CASTLING][5] = NoRights;
11317     }
11318     SendToProgram("force\n", &first);
11319     if (blackPlaysFirst) {
11320         strcpy(moveList[0], "");
11321         strcpy(parseList[0], "");
11322         currentMove = forwardMostMove = backwardMostMove = 1;
11323         CopyBoard(boards[1], boards[0]);
11324     } else {
11325         currentMove = forwardMostMove = backwardMostMove = 0;
11326     }
11327     SendBoard(&first, forwardMostMove);
11328     if (appData.debugMode) {
11329         fprintf(debugFP, "EditPosDone\n");
11330     }
11331     DisplayTitle("");
11332     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11333     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11334     gameMode = EditGame;
11335     ModeHighlight();
11336     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11337     ClearHighlights(); /* [AS] */
11338 }
11339
11340 /* Pause for `ms' milliseconds */
11341 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11342 void
11343 TimeDelay(ms)
11344      long ms;
11345 {
11346     TimeMark m1, m2;
11347
11348     GetTimeMark(&m1);
11349     do {
11350         GetTimeMark(&m2);
11351     } while (SubtractTimeMarks(&m2, &m1) < ms);
11352 }
11353
11354 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11355 void
11356 SendMultiLineToICS(buf)
11357      char *buf;
11358 {
11359     char temp[MSG_SIZ+1], *p;
11360     int len;
11361
11362     len = strlen(buf);
11363     if (len > MSG_SIZ)
11364       len = MSG_SIZ;
11365   
11366     strncpy(temp, buf, len);
11367     temp[len] = 0;
11368
11369     p = temp;
11370     while (*p) {
11371         if (*p == '\n' || *p == '\r')
11372           *p = ' ';
11373         ++p;
11374     }
11375
11376     strcat(temp, "\n");
11377     SendToICS(temp);
11378     SendToPlayer(temp, strlen(temp));
11379 }
11380
11381 void
11382 SetWhiteToPlayEvent()
11383 {
11384     if (gameMode == EditPosition) {
11385         blackPlaysFirst = FALSE;
11386         DisplayBothClocks();    /* works because currentMove is 0 */
11387     } else if (gameMode == IcsExamining) {
11388         SendToICS(ics_prefix);
11389         SendToICS("tomove white\n");
11390     }
11391 }
11392
11393 void
11394 SetBlackToPlayEvent()
11395 {
11396     if (gameMode == EditPosition) {
11397         blackPlaysFirst = TRUE;
11398         currentMove = 1;        /* kludge */
11399         DisplayBothClocks();
11400         currentMove = 0;
11401     } else if (gameMode == IcsExamining) {
11402         SendToICS(ics_prefix);
11403         SendToICS("tomove black\n");
11404     }
11405 }
11406
11407 void
11408 EditPositionMenuEvent(selection, x, y)
11409      ChessSquare selection;
11410      int x, y;
11411 {
11412     char buf[MSG_SIZ];
11413     ChessSquare piece = boards[0][y][x];
11414
11415     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11416
11417     switch (selection) {
11418       case ClearBoard:
11419         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11420             SendToICS(ics_prefix);
11421             SendToICS("bsetup clear\n");
11422         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11423             SendToICS(ics_prefix);
11424             SendToICS("clearboard\n");
11425         } else {
11426             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11427                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11428                 for (y = 0; y < BOARD_HEIGHT; y++) {
11429                     if (gameMode == IcsExamining) {
11430                         if (boards[currentMove][y][x] != EmptySquare) {
11431                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11432                                     AAA + x, ONE + y);
11433                             SendToICS(buf);
11434                         }
11435                     } else {
11436                         boards[0][y][x] = p;
11437                     }
11438                 }
11439             }
11440         }
11441         if (gameMode == EditPosition) {
11442             DrawPosition(FALSE, boards[0]);
11443         }
11444         break;
11445
11446       case WhitePlay:
11447         SetWhiteToPlayEvent();
11448         break;
11449
11450       case BlackPlay:
11451         SetBlackToPlayEvent();
11452         break;
11453
11454       case EmptySquare:
11455         if (gameMode == IcsExamining) {
11456             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11457             SendToICS(buf);
11458         } else {
11459             boards[0][y][x] = EmptySquare;
11460             DrawPosition(FALSE, boards[0]);
11461         }
11462         break;
11463
11464       case PromotePiece:
11465         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11466            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11467             selection = (ChessSquare) (PROMOTED piece);
11468         } else if(piece == EmptySquare) selection = WhiteSilver;
11469         else selection = (ChessSquare)((int)piece - 1);
11470         goto defaultlabel;
11471
11472       case DemotePiece:
11473         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11474            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11475             selection = (ChessSquare) (DEMOTED piece);
11476         } else if(piece == EmptySquare) selection = BlackSilver;
11477         else selection = (ChessSquare)((int)piece + 1);       
11478         goto defaultlabel;
11479
11480       case WhiteQueen:
11481       case BlackQueen:
11482         if(gameInfo.variant == VariantShatranj ||
11483            gameInfo.variant == VariantXiangqi  ||
11484            gameInfo.variant == VariantCourier    )
11485             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11486         goto defaultlabel;
11487
11488       case WhiteKing:
11489       case BlackKing:
11490         if(gameInfo.variant == VariantXiangqi)
11491             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11492         if(gameInfo.variant == VariantKnightmate)
11493             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11494       default:
11495         defaultlabel:
11496         if (gameMode == IcsExamining) {
11497             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11498                     PieceToChar(selection), AAA + x, ONE + y);
11499             SendToICS(buf);
11500         } else {
11501             boards[0][y][x] = selection;
11502             DrawPosition(FALSE, boards[0]);
11503         }
11504         break;
11505     }
11506 }
11507
11508
11509 void
11510 DropMenuEvent(selection, x, y)
11511      ChessSquare selection;
11512      int x, y;
11513 {
11514     ChessMove moveType;
11515
11516     switch (gameMode) {
11517       case IcsPlayingWhite:
11518       case MachinePlaysBlack:
11519         if (!WhiteOnMove(currentMove)) {
11520             DisplayMoveError(_("It is Black's turn"));
11521             return;
11522         }
11523         moveType = WhiteDrop;
11524         break;
11525       case IcsPlayingBlack:
11526       case MachinePlaysWhite:
11527         if (WhiteOnMove(currentMove)) {
11528             DisplayMoveError(_("It is White's turn"));
11529             return;
11530         }
11531         moveType = BlackDrop;
11532         break;
11533       case EditGame:
11534         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11535         break;
11536       default:
11537         return;
11538     }
11539
11540     if (moveType == BlackDrop && selection < BlackPawn) {
11541       selection = (ChessSquare) ((int) selection
11542                                  + (int) BlackPawn - (int) WhitePawn);
11543     }
11544     if (boards[currentMove][y][x] != EmptySquare) {
11545         DisplayMoveError(_("That square is occupied"));
11546         return;
11547     }
11548
11549     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11550 }
11551
11552 void
11553 AcceptEvent()
11554 {
11555     /* Accept a pending offer of any kind from opponent */
11556     
11557     if (appData.icsActive) {
11558         SendToICS(ics_prefix);
11559         SendToICS("accept\n");
11560     } else if (cmailMsgLoaded) {
11561         if (currentMove == cmailOldMove &&
11562             commentList[cmailOldMove] != NULL &&
11563             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11564                    "Black offers a draw" : "White offers a draw")) {
11565             TruncateGame();
11566             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11567             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11568         } else {
11569             DisplayError(_("There is no pending offer on this move"), 0);
11570             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11571         }
11572     } else {
11573         /* Not used for offers from chess program */
11574     }
11575 }
11576
11577 void
11578 DeclineEvent()
11579 {
11580     /* Decline a pending offer of any kind from opponent */
11581     
11582     if (appData.icsActive) {
11583         SendToICS(ics_prefix);
11584         SendToICS("decline\n");
11585     } else if (cmailMsgLoaded) {
11586         if (currentMove == cmailOldMove &&
11587             commentList[cmailOldMove] != NULL &&
11588             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11589                    "Black offers a draw" : "White offers a draw")) {
11590 #ifdef NOTDEF
11591             AppendComment(cmailOldMove, "Draw declined", TRUE);
11592             DisplayComment(cmailOldMove - 1, "Draw declined");
11593 #endif /*NOTDEF*/
11594         } else {
11595             DisplayError(_("There is no pending offer on this move"), 0);
11596         }
11597     } else {
11598         /* Not used for offers from chess program */
11599     }
11600 }
11601
11602 void
11603 RematchEvent()
11604 {
11605     /* Issue ICS rematch command */
11606     if (appData.icsActive) {
11607         SendToICS(ics_prefix);
11608         SendToICS("rematch\n");
11609     }
11610 }
11611
11612 void
11613 CallFlagEvent()
11614 {
11615     /* Call your opponent's flag (claim a win on time) */
11616     if (appData.icsActive) {
11617         SendToICS(ics_prefix);
11618         SendToICS("flag\n");
11619     } else {
11620         switch (gameMode) {
11621           default:
11622             return;
11623           case MachinePlaysWhite:
11624             if (whiteFlag) {
11625                 if (blackFlag)
11626                   GameEnds(GameIsDrawn, "Both players ran out of time",
11627                            GE_PLAYER);
11628                 else
11629                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11630             } else {
11631                 DisplayError(_("Your opponent is not out of time"), 0);
11632             }
11633             break;
11634           case MachinePlaysBlack:
11635             if (blackFlag) {
11636                 if (whiteFlag)
11637                   GameEnds(GameIsDrawn, "Both players ran out of time",
11638                            GE_PLAYER);
11639                 else
11640                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11641             } else {
11642                 DisplayError(_("Your opponent is not out of time"), 0);
11643             }
11644             break;
11645         }
11646     }
11647 }
11648
11649 void
11650 DrawEvent()
11651 {
11652     /* Offer draw or accept pending draw offer from opponent */
11653     
11654     if (appData.icsActive) {
11655         /* Note: tournament rules require draw offers to be
11656            made after you make your move but before you punch
11657            your clock.  Currently ICS doesn't let you do that;
11658            instead, you immediately punch your clock after making
11659            a move, but you can offer a draw at any time. */
11660         
11661         SendToICS(ics_prefix);
11662         SendToICS("draw\n");
11663     } else if (cmailMsgLoaded) {
11664         if (currentMove == cmailOldMove &&
11665             commentList[cmailOldMove] != NULL &&
11666             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11667                    "Black offers a draw" : "White offers a draw")) {
11668             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11669             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11670         } else if (currentMove == cmailOldMove + 1) {
11671             char *offer = WhiteOnMove(cmailOldMove) ?
11672               "White offers a draw" : "Black offers a draw";
11673             AppendComment(currentMove, offer, TRUE);
11674             DisplayComment(currentMove - 1, offer);
11675             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11676         } else {
11677             DisplayError(_("You must make your move before offering a draw"), 0);
11678             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11679         }
11680     } else if (first.offeredDraw) {
11681         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11682     } else {
11683         if (first.sendDrawOffers) {
11684             SendToProgram("draw\n", &first);
11685             userOfferedDraw = TRUE;
11686         }
11687     }
11688 }
11689
11690 void
11691 AdjournEvent()
11692 {
11693     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11694     
11695     if (appData.icsActive) {
11696         SendToICS(ics_prefix);
11697         SendToICS("adjourn\n");
11698     } else {
11699         /* Currently GNU Chess doesn't offer or accept Adjourns */
11700     }
11701 }
11702
11703
11704 void
11705 AbortEvent()
11706 {
11707     /* Offer Abort or accept pending Abort offer from opponent */
11708     
11709     if (appData.icsActive) {
11710         SendToICS(ics_prefix);
11711         SendToICS("abort\n");
11712     } else {
11713         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11714     }
11715 }
11716
11717 void
11718 ResignEvent()
11719 {
11720     /* Resign.  You can do this even if it's not your turn. */
11721     
11722     if (appData.icsActive) {
11723         SendToICS(ics_prefix);
11724         SendToICS("resign\n");
11725     } else {
11726         switch (gameMode) {
11727           case MachinePlaysWhite:
11728             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11729             break;
11730           case MachinePlaysBlack:
11731             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11732             break;
11733           case EditGame:
11734             if (cmailMsgLoaded) {
11735                 TruncateGame();
11736                 if (WhiteOnMove(cmailOldMove)) {
11737                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11738                 } else {
11739                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11740                 }
11741                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11742             }
11743             break;
11744           default:
11745             break;
11746         }
11747     }
11748 }
11749
11750
11751 void
11752 StopObservingEvent()
11753 {
11754     /* Stop observing current games */
11755     SendToICS(ics_prefix);
11756     SendToICS("unobserve\n");
11757 }
11758
11759 void
11760 StopExaminingEvent()
11761 {
11762     /* Stop observing current game */
11763     SendToICS(ics_prefix);
11764     SendToICS("unexamine\n");
11765 }
11766
11767 void
11768 ForwardInner(target)
11769      int target;
11770 {
11771     int limit;
11772
11773     if (appData.debugMode)
11774         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11775                 target, currentMove, forwardMostMove);
11776
11777     if (gameMode == EditPosition)
11778       return;
11779
11780     if (gameMode == PlayFromGameFile && !pausing)
11781       PauseEvent();
11782     
11783     if (gameMode == IcsExamining && pausing)
11784       limit = pauseExamForwardMostMove;
11785     else
11786       limit = forwardMostMove;
11787     
11788     if (target > limit) target = limit;
11789
11790     if (target > 0 && moveList[target - 1][0]) {
11791         int fromX, fromY, toX, toY;
11792         toX = moveList[target - 1][2] - AAA;
11793         toY = moveList[target - 1][3] - ONE;
11794         if (moveList[target - 1][1] == '@') {
11795             if (appData.highlightLastMove) {
11796                 SetHighlights(-1, -1, toX, toY);
11797             }
11798         } else {
11799             fromX = moveList[target - 1][0] - AAA;
11800             fromY = moveList[target - 1][1] - ONE;
11801             if (target == currentMove + 1) {
11802                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11803             }
11804             if (appData.highlightLastMove) {
11805                 SetHighlights(fromX, fromY, toX, toY);
11806             }
11807         }
11808     }
11809     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11810         gameMode == Training || gameMode == PlayFromGameFile || 
11811         gameMode == AnalyzeFile) {
11812         while (currentMove < target) {
11813             SendMoveToProgram(currentMove++, &first);
11814         }
11815     } else {
11816         currentMove = target;
11817     }
11818     
11819     if (gameMode == EditGame || gameMode == EndOfGame) {
11820         whiteTimeRemaining = timeRemaining[0][currentMove];
11821         blackTimeRemaining = timeRemaining[1][currentMove];
11822     }
11823     DisplayBothClocks();
11824     DisplayMove(currentMove - 1);
11825     DrawPosition(FALSE, boards[currentMove]);
11826     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11827     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11828         DisplayComment(currentMove - 1, commentList[currentMove]);
11829     }
11830 }
11831
11832
11833 void
11834 ForwardEvent()
11835 {
11836     if (gameMode == IcsExamining && !pausing) {
11837         SendToICS(ics_prefix);
11838         SendToICS("forward\n");
11839     } else {
11840         ForwardInner(currentMove + 1);
11841     }
11842 }
11843
11844 void
11845 ToEndEvent()
11846 {
11847     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11848         /* to optimze, we temporarily turn off analysis mode while we feed
11849          * the remaining moves to the engine. Otherwise we get analysis output
11850          * after each move.
11851          */ 
11852         if (first.analysisSupport) {
11853           SendToProgram("exit\nforce\n", &first);
11854           first.analyzing = FALSE;
11855         }
11856     }
11857         
11858     if (gameMode == IcsExamining && !pausing) {
11859         SendToICS(ics_prefix);
11860         SendToICS("forward 999999\n");
11861     } else {
11862         ForwardInner(forwardMostMove);
11863     }
11864
11865     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11866         /* we have fed all the moves, so reactivate analysis mode */
11867         SendToProgram("analyze\n", &first);
11868         first.analyzing = TRUE;
11869         /*first.maybeThinking = TRUE;*/
11870         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11871     }
11872 }
11873
11874 void
11875 BackwardInner(target)
11876      int target;
11877 {
11878     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11879
11880     if (appData.debugMode)
11881         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11882                 target, currentMove, forwardMostMove);
11883
11884     if (gameMode == EditPosition) return;
11885     if (currentMove <= backwardMostMove) {
11886         ClearHighlights();
11887         DrawPosition(full_redraw, boards[currentMove]);
11888         return;
11889     }
11890     if (gameMode == PlayFromGameFile && !pausing)
11891       PauseEvent();
11892     
11893     if (moveList[target][0]) {
11894         int fromX, fromY, toX, toY;
11895         toX = moveList[target][2] - AAA;
11896         toY = moveList[target][3] - ONE;
11897         if (moveList[target][1] == '@') {
11898             if (appData.highlightLastMove) {
11899                 SetHighlights(-1, -1, toX, toY);
11900             }
11901         } else {
11902             fromX = moveList[target][0] - AAA;
11903             fromY = moveList[target][1] - ONE;
11904             if (target == currentMove - 1) {
11905                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11906             }
11907             if (appData.highlightLastMove) {
11908                 SetHighlights(fromX, fromY, toX, toY);
11909             }
11910         }
11911     }
11912     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11913         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11914         while (currentMove > target) {
11915             SendToProgram("undo\n", &first);
11916             currentMove--;
11917         }
11918     } else {
11919         currentMove = target;
11920     }
11921     
11922     if (gameMode == EditGame || gameMode == EndOfGame) {
11923         whiteTimeRemaining = timeRemaining[0][currentMove];
11924         blackTimeRemaining = timeRemaining[1][currentMove];
11925     }
11926     DisplayBothClocks();
11927     DisplayMove(currentMove - 1);
11928     DrawPosition(full_redraw, boards[currentMove]);
11929     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11930     // [HGM] PV info: routine tests if comment empty
11931     DisplayComment(currentMove - 1, commentList[currentMove]);
11932 }
11933
11934 void
11935 BackwardEvent()
11936 {
11937     if (gameMode == IcsExamining && !pausing) {
11938         SendToICS(ics_prefix);
11939         SendToICS("backward\n");
11940     } else {
11941         BackwardInner(currentMove - 1);
11942     }
11943 }
11944
11945 void
11946 ToStartEvent()
11947 {
11948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11949         /* to optimize, we temporarily turn off analysis mode while we undo
11950          * all the moves. Otherwise we get analysis output after each undo.
11951          */ 
11952         if (first.analysisSupport) {
11953           SendToProgram("exit\nforce\n", &first);
11954           first.analyzing = FALSE;
11955         }
11956     }
11957
11958     if (gameMode == IcsExamining && !pausing) {
11959         SendToICS(ics_prefix);
11960         SendToICS("backward 999999\n");
11961     } else {
11962         BackwardInner(backwardMostMove);
11963     }
11964
11965     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11966         /* we have fed all the moves, so reactivate analysis mode */
11967         SendToProgram("analyze\n", &first);
11968         first.analyzing = TRUE;
11969         /*first.maybeThinking = TRUE;*/
11970         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11971     }
11972 }
11973
11974 void
11975 ToNrEvent(int to)
11976 {
11977   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11978   if (to >= forwardMostMove) to = forwardMostMove;
11979   if (to <= backwardMostMove) to = backwardMostMove;
11980   if (to < currentMove) {
11981     BackwardInner(to);
11982   } else {
11983     ForwardInner(to);
11984   }
11985 }
11986
11987 void
11988 RevertEvent()
11989 {
11990     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11991         return;
11992     }
11993     if (gameMode != IcsExamining) {
11994         DisplayError(_("You are not examining a game"), 0);
11995         return;
11996     }
11997     if (pausing) {
11998         DisplayError(_("You can't revert while pausing"), 0);
11999         return;
12000     }
12001     SendToICS(ics_prefix);
12002     SendToICS("revert\n");
12003 }
12004
12005 void
12006 RetractMoveEvent()
12007 {
12008     switch (gameMode) {
12009       case MachinePlaysWhite:
12010       case MachinePlaysBlack:
12011         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12012             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12013             return;
12014         }
12015         if (forwardMostMove < 2) return;
12016         currentMove = forwardMostMove = forwardMostMove - 2;
12017         whiteTimeRemaining = timeRemaining[0][currentMove];
12018         blackTimeRemaining = timeRemaining[1][currentMove];
12019         DisplayBothClocks();
12020         DisplayMove(currentMove - 1);
12021         ClearHighlights();/*!! could figure this out*/
12022         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12023         SendToProgram("remove\n", &first);
12024         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12025         break;
12026
12027       case BeginningOfGame:
12028       default:
12029         break;
12030
12031       case IcsPlayingWhite:
12032       case IcsPlayingBlack:
12033         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12034             SendToICS(ics_prefix);
12035             SendToICS("takeback 2\n");
12036         } else {
12037             SendToICS(ics_prefix);
12038             SendToICS("takeback 1\n");
12039         }
12040         break;
12041     }
12042 }
12043
12044 void
12045 MoveNowEvent()
12046 {
12047     ChessProgramState *cps;
12048
12049     switch (gameMode) {
12050       case MachinePlaysWhite:
12051         if (!WhiteOnMove(forwardMostMove)) {
12052             DisplayError(_("It is your turn"), 0);
12053             return;
12054         }
12055         cps = &first;
12056         break;
12057       case MachinePlaysBlack:
12058         if (WhiteOnMove(forwardMostMove)) {
12059             DisplayError(_("It is your turn"), 0);
12060             return;
12061         }
12062         cps = &first;
12063         break;
12064       case TwoMachinesPlay:
12065         if (WhiteOnMove(forwardMostMove) ==
12066             (first.twoMachinesColor[0] == 'w')) {
12067             cps = &first;
12068         } else {
12069             cps = &second;
12070         }
12071         break;
12072       case BeginningOfGame:
12073       default:
12074         return;
12075     }
12076     SendToProgram("?\n", cps);
12077 }
12078
12079 void
12080 TruncateGameEvent()
12081 {
12082     EditGameEvent();
12083     if (gameMode != EditGame) return;
12084     TruncateGame();
12085 }
12086
12087 void
12088 TruncateGame()
12089 {
12090     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12091     if (forwardMostMove > currentMove) {
12092         if (gameInfo.resultDetails != NULL) {
12093             free(gameInfo.resultDetails);
12094             gameInfo.resultDetails = NULL;
12095             gameInfo.result = GameUnfinished;
12096         }
12097         forwardMostMove = currentMove;
12098         HistorySet(parseList, backwardMostMove, forwardMostMove,
12099                    currentMove-1);
12100     }
12101 }
12102
12103 void
12104 HintEvent()
12105 {
12106     if (appData.noChessProgram) return;
12107     switch (gameMode) {
12108       case MachinePlaysWhite:
12109         if (WhiteOnMove(forwardMostMove)) {
12110             DisplayError(_("Wait until your turn"), 0);
12111             return;
12112         }
12113         break;
12114       case BeginningOfGame:
12115       case MachinePlaysBlack:
12116         if (!WhiteOnMove(forwardMostMove)) {
12117             DisplayError(_("Wait until your turn"), 0);
12118             return;
12119         }
12120         break;
12121       default:
12122         DisplayError(_("No hint available"), 0);
12123         return;
12124     }
12125     SendToProgram("hint\n", &first);
12126     hintRequested = TRUE;
12127 }
12128
12129 void
12130 BookEvent()
12131 {
12132     if (appData.noChessProgram) return;
12133     switch (gameMode) {
12134       case MachinePlaysWhite:
12135         if (WhiteOnMove(forwardMostMove)) {
12136             DisplayError(_("Wait until your turn"), 0);
12137             return;
12138         }
12139         break;
12140       case BeginningOfGame:
12141       case MachinePlaysBlack:
12142         if (!WhiteOnMove(forwardMostMove)) {
12143             DisplayError(_("Wait until your turn"), 0);
12144             return;
12145         }
12146         break;
12147       case EditPosition:
12148         EditPositionDone(TRUE);
12149         break;
12150       case TwoMachinesPlay:
12151         return;
12152       default:
12153         break;
12154     }
12155     SendToProgram("bk\n", &first);
12156     bookOutput[0] = NULLCHAR;
12157     bookRequested = TRUE;
12158 }
12159
12160 void
12161 AboutGameEvent()
12162 {
12163     char *tags = PGNTags(&gameInfo);
12164     TagsPopUp(tags, CmailMsg());
12165     free(tags);
12166 }
12167
12168 /* end button procedures */
12169
12170 void
12171 PrintPosition(fp, move)
12172      FILE *fp;
12173      int move;
12174 {
12175     int i, j;
12176     
12177     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12178         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12179             char c = PieceToChar(boards[move][i][j]);
12180             fputc(c == 'x' ? '.' : c, fp);
12181             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12182         }
12183     }
12184     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12185       fprintf(fp, "white to play\n");
12186     else
12187       fprintf(fp, "black to play\n");
12188 }
12189
12190 void
12191 PrintOpponents(fp)
12192      FILE *fp;
12193 {
12194     if (gameInfo.white != NULL) {
12195         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12196     } else {
12197         fprintf(fp, "\n");
12198     }
12199 }
12200
12201 /* Find last component of program's own name, using some heuristics */
12202 void
12203 TidyProgramName(prog, host, buf)
12204      char *prog, *host, buf[MSG_SIZ];
12205 {
12206     char *p, *q;
12207     int local = (strcmp(host, "localhost") == 0);
12208     while (!local && (p = strchr(prog, ';')) != NULL) {
12209         p++;
12210         while (*p == ' ') p++;
12211         prog = p;
12212     }
12213     if (*prog == '"' || *prog == '\'') {
12214         q = strchr(prog + 1, *prog);
12215     } else {
12216         q = strchr(prog, ' ');
12217     }
12218     if (q == NULL) q = prog + strlen(prog);
12219     p = q;
12220     while (p >= prog && *p != '/' && *p != '\\') p--;
12221     p++;
12222     if(p == prog && *p == '"') p++;
12223     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12224     memcpy(buf, p, q - p);
12225     buf[q - p] = NULLCHAR;
12226     if (!local) {
12227         strcat(buf, "@");
12228         strcat(buf, host);
12229     }
12230 }
12231
12232 char *
12233 TimeControlTagValue()
12234 {
12235     char buf[MSG_SIZ];
12236     if (!appData.clockMode) {
12237         strcpy(buf, "-");
12238     } else if (movesPerSession > 0) {
12239         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12240     } else if (timeIncrement == 0) {
12241         sprintf(buf, "%ld", timeControl/1000);
12242     } else {
12243         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12244     }
12245     return StrSave(buf);
12246 }
12247
12248 void
12249 SetGameInfo()
12250 {
12251     /* This routine is used only for certain modes */
12252     VariantClass v = gameInfo.variant;
12253     ChessMove r = GameUnfinished;
12254     char *p = NULL;
12255
12256     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12257         r = gameInfo.result; 
12258         p = gameInfo.resultDetails; 
12259         gameInfo.resultDetails = NULL;
12260     }
12261     ClearGameInfo(&gameInfo);
12262     gameInfo.variant = v;
12263
12264     switch (gameMode) {
12265       case MachinePlaysWhite:
12266         gameInfo.event = StrSave( appData.pgnEventHeader );
12267         gameInfo.site = StrSave(HostName());
12268         gameInfo.date = PGNDate();
12269         gameInfo.round = StrSave("-");
12270         gameInfo.white = StrSave(first.tidy);
12271         gameInfo.black = StrSave(UserName());
12272         gameInfo.timeControl = TimeControlTagValue();
12273         break;
12274
12275       case MachinePlaysBlack:
12276         gameInfo.event = StrSave( appData.pgnEventHeader );
12277         gameInfo.site = StrSave(HostName());
12278         gameInfo.date = PGNDate();
12279         gameInfo.round = StrSave("-");
12280         gameInfo.white = StrSave(UserName());
12281         gameInfo.black = StrSave(first.tidy);
12282         gameInfo.timeControl = TimeControlTagValue();
12283         break;
12284
12285       case TwoMachinesPlay:
12286         gameInfo.event = StrSave( appData.pgnEventHeader );
12287         gameInfo.site = StrSave(HostName());
12288         gameInfo.date = PGNDate();
12289         if (matchGame > 0) {
12290             char buf[MSG_SIZ];
12291             sprintf(buf, "%d", matchGame);
12292             gameInfo.round = StrSave(buf);
12293         } else {
12294             gameInfo.round = StrSave("-");
12295         }
12296         if (first.twoMachinesColor[0] == 'w') {
12297             gameInfo.white = StrSave(first.tidy);
12298             gameInfo.black = StrSave(second.tidy);
12299         } else {
12300             gameInfo.white = StrSave(second.tidy);
12301             gameInfo.black = StrSave(first.tidy);
12302         }
12303         gameInfo.timeControl = TimeControlTagValue();
12304         break;
12305
12306       case EditGame:
12307         gameInfo.event = StrSave("Edited game");
12308         gameInfo.site = StrSave(HostName());
12309         gameInfo.date = PGNDate();
12310         gameInfo.round = StrSave("-");
12311         gameInfo.white = StrSave("-");
12312         gameInfo.black = StrSave("-");
12313         gameInfo.result = r;
12314         gameInfo.resultDetails = p;
12315         break;
12316
12317       case EditPosition:
12318         gameInfo.event = StrSave("Edited position");
12319         gameInfo.site = StrSave(HostName());
12320         gameInfo.date = PGNDate();
12321         gameInfo.round = StrSave("-");
12322         gameInfo.white = StrSave("-");
12323         gameInfo.black = StrSave("-");
12324         break;
12325
12326       case IcsPlayingWhite:
12327       case IcsPlayingBlack:
12328       case IcsObserving:
12329       case IcsExamining:
12330         break;
12331
12332       case PlayFromGameFile:
12333         gameInfo.event = StrSave("Game from non-PGN file");
12334         gameInfo.site = StrSave(HostName());
12335         gameInfo.date = PGNDate();
12336         gameInfo.round = StrSave("-");
12337         gameInfo.white = StrSave("?");
12338         gameInfo.black = StrSave("?");
12339         break;
12340
12341       default:
12342         break;
12343     }
12344 }
12345
12346 void
12347 ReplaceComment(index, text)
12348      int index;
12349      char *text;
12350 {
12351     int len;
12352
12353     while (*text == '\n') text++;
12354     len = strlen(text);
12355     while (len > 0 && text[len - 1] == '\n') len--;
12356
12357     if (commentList[index] != NULL)
12358       free(commentList[index]);
12359
12360     if (len == 0) {
12361         commentList[index] = NULL;
12362         return;
12363     }
12364   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12365       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12366       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12367     commentList[index] = (char *) malloc(len + 2);
12368     strncpy(commentList[index], text, len);
12369     commentList[index][len] = '\n';
12370     commentList[index][len + 1] = NULLCHAR;
12371   } else { 
12372     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12373     char *p;
12374     commentList[index] = (char *) malloc(len + 6);
12375     strcpy(commentList[index], "{\n");
12376     strncpy(commentList[index]+2, text, len);
12377     commentList[index][len+2] = NULLCHAR;
12378     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12379     strcat(commentList[index], "\n}\n");
12380   }
12381 }
12382
12383 void
12384 CrushCRs(text)
12385      char *text;
12386 {
12387   char *p = text;
12388   char *q = text;
12389   char ch;
12390
12391   do {
12392     ch = *p++;
12393     if (ch == '\r') continue;
12394     *q++ = ch;
12395   } while (ch != '\0');
12396 }
12397
12398 void
12399 AppendComment(index, text, addBraces)
12400      int index;
12401      char *text;
12402      Boolean addBraces; // [HGM] braces: tells if we should add {}
12403 {
12404     int oldlen, len;
12405     char *old;
12406
12407 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12408     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12409
12410     CrushCRs(text);
12411     while (*text == '\n') text++;
12412     len = strlen(text);
12413     while (len > 0 && text[len - 1] == '\n') len--;
12414
12415     if (len == 0) return;
12416
12417     if (commentList[index] != NULL) {
12418         old = commentList[index];
12419         oldlen = strlen(old);
12420         while(commentList[index][oldlen-1] ==  '\n')
12421           commentList[index][--oldlen] = NULLCHAR;
12422         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12423         strcpy(commentList[index], old);
12424         free(old);
12425         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12426         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12427           if(addBraces) addBraces = FALSE; else { text++; len--; }
12428           while (*text == '\n') { text++; len--; }
12429           commentList[index][--oldlen] = NULLCHAR;
12430       }
12431         if(addBraces) strcat(commentList[index], "\n{\n");
12432         else          strcat(commentList[index], "\n");
12433         strcat(commentList[index], text);
12434         if(addBraces) strcat(commentList[index], "\n}\n");
12435         else          strcat(commentList[index], "\n");
12436     } else {
12437         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12438         if(addBraces)
12439              strcpy(commentList[index], "{\n");
12440         else commentList[index][0] = NULLCHAR;
12441         strcat(commentList[index], text);
12442         strcat(commentList[index], "\n");
12443         if(addBraces) strcat(commentList[index], "}\n");
12444     }
12445 }
12446
12447 static char * FindStr( char * text, char * sub_text )
12448 {
12449     char * result = strstr( text, sub_text );
12450
12451     if( result != NULL ) {
12452         result += strlen( sub_text );
12453     }
12454
12455     return result;
12456 }
12457
12458 /* [AS] Try to extract PV info from PGN comment */
12459 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12460 char *GetInfoFromComment( int index, char * text )
12461 {
12462     char * sep = text;
12463
12464     if( text != NULL && index > 0 ) {
12465         int score = 0;
12466         int depth = 0;
12467         int time = -1, sec = 0, deci;
12468         char * s_eval = FindStr( text, "[%eval " );
12469         char * s_emt = FindStr( text, "[%emt " );
12470
12471         if( s_eval != NULL || s_emt != NULL ) {
12472             /* New style */
12473             char delim;
12474
12475             if( s_eval != NULL ) {
12476                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12477                     return text;
12478                 }
12479
12480                 if( delim != ']' ) {
12481                     return text;
12482                 }
12483             }
12484
12485             if( s_emt != NULL ) {
12486             }
12487                 return text;
12488         }
12489         else {
12490             /* We expect something like: [+|-]nnn.nn/dd */
12491             int score_lo = 0;
12492
12493             if(*text != '{') return text; // [HGM] braces: must be normal comment
12494
12495             sep = strchr( text, '/' );
12496             if( sep == NULL || sep < (text+4) ) {
12497                 return text;
12498             }
12499
12500             time = -1; sec = -1; deci = -1;
12501             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12502                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12503                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12504                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12505                 return text;
12506             }
12507
12508             if( score_lo < 0 || score_lo >= 100 ) {
12509                 return text;
12510             }
12511
12512             if(sec >= 0) time = 600*time + 10*sec; else
12513             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12514
12515             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12516
12517             /* [HGM] PV time: now locate end of PV info */
12518             while( *++sep >= '0' && *sep <= '9'); // strip depth
12519             if(time >= 0)
12520             while( *++sep >= '0' && *sep <= '9'); // strip time
12521             if(sec >= 0)
12522             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12523             if(deci >= 0)
12524             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12525             while(*sep == ' ') sep++;
12526         }
12527
12528         if( depth <= 0 ) {
12529             return text;
12530         }
12531
12532         if( time < 0 ) {
12533             time = -1;
12534         }
12535
12536         pvInfoList[index-1].depth = depth;
12537         pvInfoList[index-1].score = score;
12538         pvInfoList[index-1].time  = 10*time; // centi-sec
12539         if(*sep == '}') *sep = 0; else *--sep = '{';
12540     }
12541     return sep;
12542 }
12543
12544 void
12545 SendToProgram(message, cps)
12546      char *message;
12547      ChessProgramState *cps;
12548 {
12549     int count, outCount, error;
12550     char buf[MSG_SIZ];
12551
12552     if (cps->pr == NULL) return;
12553     Attention(cps);
12554     
12555     if (appData.debugMode) {
12556         TimeMark now;
12557         GetTimeMark(&now);
12558         fprintf(debugFP, "%ld >%-6s: %s", 
12559                 SubtractTimeMarks(&now, &programStartTime),
12560                 cps->which, message);
12561     }
12562     
12563     count = strlen(message);
12564     outCount = OutputToProcess(cps->pr, message, count, &error);
12565     if (outCount < count && !exiting 
12566                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12567         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12568         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12569             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12570                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12571                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12572             } else {
12573                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12574             }
12575             gameInfo.resultDetails = StrSave(buf);
12576         }
12577         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12578     }
12579 }
12580
12581 void
12582 ReceiveFromProgram(isr, closure, message, count, error)
12583      InputSourceRef isr;
12584      VOIDSTAR closure;
12585      char *message;
12586      int count;
12587      int error;
12588 {
12589     char *end_str;
12590     char buf[MSG_SIZ];
12591     ChessProgramState *cps = (ChessProgramState *)closure;
12592
12593     if (isr != cps->isr) return; /* Killed intentionally */
12594     if (count <= 0) {
12595         if (count == 0) {
12596             sprintf(buf,
12597                     _("Error: %s chess program (%s) exited unexpectedly"),
12598                     cps->which, cps->program);
12599         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12600                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12601                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12602                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12603                 } else {
12604                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12605                 }
12606                 gameInfo.resultDetails = StrSave(buf);
12607             }
12608             RemoveInputSource(cps->isr);
12609             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12610         } else {
12611             sprintf(buf,
12612                     _("Error reading from %s chess program (%s)"),
12613                     cps->which, cps->program);
12614             RemoveInputSource(cps->isr);
12615
12616             /* [AS] Program is misbehaving badly... kill it */
12617             if( count == -2 ) {
12618                 DestroyChildProcess( cps->pr, 9 );
12619                 cps->pr = NoProc;
12620             }
12621
12622             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12623         }
12624         return;
12625     }
12626     
12627     if ((end_str = strchr(message, '\r')) != NULL)
12628       *end_str = NULLCHAR;
12629     if ((end_str = strchr(message, '\n')) != NULL)
12630       *end_str = NULLCHAR;
12631     
12632     if (appData.debugMode) {
12633         TimeMark now; int print = 1;
12634         char *quote = ""; char c; int i;
12635
12636         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12637                 char start = message[0];
12638                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12639                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12640                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12641                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12642                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12643                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12644                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12645                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12646                         { quote = "# "; print = (appData.engineComments == 2); }
12647                 message[0] = start; // restore original message
12648         }
12649         if(print) {
12650                 GetTimeMark(&now);
12651                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12652                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12653                         quote,
12654                         message);
12655         }
12656     }
12657
12658     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12659     if (appData.icsEngineAnalyze) {
12660         if (strstr(message, "whisper") != NULL ||
12661              strstr(message, "kibitz") != NULL || 
12662             strstr(message, "tellics") != NULL) return;
12663     }
12664
12665     HandleMachineMove(message, cps);
12666 }
12667
12668
12669 void
12670 SendTimeControl(cps, mps, tc, inc, sd, st)
12671      ChessProgramState *cps;
12672      int mps, inc, sd, st;
12673      long tc;
12674 {
12675     char buf[MSG_SIZ];
12676     int seconds;
12677
12678     if( timeControl_2 > 0 ) {
12679         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12680             tc = timeControl_2;
12681         }
12682     }
12683     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12684     inc /= cps->timeOdds;
12685     st  /= cps->timeOdds;
12686
12687     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12688
12689     if (st > 0) {
12690       /* Set exact time per move, normally using st command */
12691       if (cps->stKludge) {
12692         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12693         seconds = st % 60;
12694         if (seconds == 0) {
12695           sprintf(buf, "level 1 %d\n", st/60);
12696         } else {
12697           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12698         }
12699       } else {
12700         sprintf(buf, "st %d\n", st);
12701       }
12702     } else {
12703       /* Set conventional or incremental time control, using level command */
12704       if (seconds == 0) {
12705         /* Note old gnuchess bug -- minutes:seconds used to not work.
12706            Fixed in later versions, but still avoid :seconds
12707            when seconds is 0. */
12708         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12709       } else {
12710         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12711                 seconds, inc/1000);
12712       }
12713     }
12714     SendToProgram(buf, cps);
12715
12716     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12717     /* Orthogonally, limit search to given depth */
12718     if (sd > 0) {
12719       if (cps->sdKludge) {
12720         sprintf(buf, "depth\n%d\n", sd);
12721       } else {
12722         sprintf(buf, "sd %d\n", sd);
12723       }
12724       SendToProgram(buf, cps);
12725     }
12726
12727     if(cps->nps > 0) { /* [HGM] nps */
12728         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12729         else {
12730                 sprintf(buf, "nps %d\n", cps->nps);
12731               SendToProgram(buf, cps);
12732         }
12733     }
12734 }
12735
12736 ChessProgramState *WhitePlayer()
12737 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12738 {
12739     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12740        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12741         return &second;
12742     return &first;
12743 }
12744
12745 void
12746 SendTimeRemaining(cps, machineWhite)
12747      ChessProgramState *cps;
12748      int /*boolean*/ machineWhite;
12749 {
12750     char message[MSG_SIZ];
12751     long time, otime;
12752
12753     /* Note: this routine must be called when the clocks are stopped
12754        or when they have *just* been set or switched; otherwise
12755        it will be off by the time since the current tick started.
12756     */
12757     if (machineWhite) {
12758         time = whiteTimeRemaining / 10;
12759         otime = blackTimeRemaining / 10;
12760     } else {
12761         time = blackTimeRemaining / 10;
12762         otime = whiteTimeRemaining / 10;
12763     }
12764     /* [HGM] translate opponent's time by time-odds factor */
12765     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12766     if (appData.debugMode) {
12767         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12768     }
12769
12770     if (time <= 0) time = 1;
12771     if (otime <= 0) otime = 1;
12772     
12773     sprintf(message, "time %ld\n", time);
12774     SendToProgram(message, cps);
12775
12776     sprintf(message, "otim %ld\n", otime);
12777     SendToProgram(message, cps);
12778 }
12779
12780 int
12781 BoolFeature(p, name, loc, cps)
12782      char **p;
12783      char *name;
12784      int *loc;
12785      ChessProgramState *cps;
12786 {
12787   char buf[MSG_SIZ];
12788   int len = strlen(name);
12789   int val;
12790   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12791     (*p) += len + 1;
12792     sscanf(*p, "%d", &val);
12793     *loc = (val != 0);
12794     while (**p && **p != ' ') (*p)++;
12795     sprintf(buf, "accepted %s\n", name);
12796     SendToProgram(buf, cps);
12797     return TRUE;
12798   }
12799   return FALSE;
12800 }
12801
12802 int
12803 IntFeature(p, name, loc, cps)
12804      char **p;
12805      char *name;
12806      int *loc;
12807      ChessProgramState *cps;
12808 {
12809   char buf[MSG_SIZ];
12810   int len = strlen(name);
12811   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12812     (*p) += len + 1;
12813     sscanf(*p, "%d", loc);
12814     while (**p && **p != ' ') (*p)++;
12815     sprintf(buf, "accepted %s\n", name);
12816     SendToProgram(buf, cps);
12817     return TRUE;
12818   }
12819   return FALSE;
12820 }
12821
12822 int
12823 StringFeature(p, name, loc, cps)
12824      char **p;
12825      char *name;
12826      char loc[];
12827      ChessProgramState *cps;
12828 {
12829   char buf[MSG_SIZ];
12830   int len = strlen(name);
12831   if (strncmp((*p), name, len) == 0
12832       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12833     (*p) += len + 2;
12834     sscanf(*p, "%[^\"]", loc);
12835     while (**p && **p != '\"') (*p)++;
12836     if (**p == '\"') (*p)++;
12837     sprintf(buf, "accepted %s\n", name);
12838     SendToProgram(buf, cps);
12839     return TRUE;
12840   }
12841   return FALSE;
12842 }
12843
12844 int 
12845 ParseOption(Option *opt, ChessProgramState *cps)
12846 // [HGM] options: process the string that defines an engine option, and determine
12847 // name, type, default value, and allowed value range
12848 {
12849         char *p, *q, buf[MSG_SIZ];
12850         int n, min = (-1)<<31, max = 1<<31, def;
12851
12852         if(p = strstr(opt->name, " -spin ")) {
12853             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12854             if(max < min) max = min; // enforce consistency
12855             if(def < min) def = min;
12856             if(def > max) def = max;
12857             opt->value = def;
12858             opt->min = min;
12859             opt->max = max;
12860             opt->type = Spin;
12861         } else if((p = strstr(opt->name, " -slider "))) {
12862             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12863             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12864             if(max < min) max = min; // enforce consistency
12865             if(def < min) def = min;
12866             if(def > max) def = max;
12867             opt->value = def;
12868             opt->min = min;
12869             opt->max = max;
12870             opt->type = Spin; // Slider;
12871         } else if((p = strstr(opt->name, " -string "))) {
12872             opt->textValue = p+9;
12873             opt->type = TextBox;
12874         } else if((p = strstr(opt->name, " -file "))) {
12875             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12876             opt->textValue = p+7;
12877             opt->type = TextBox; // FileName;
12878         } else if((p = strstr(opt->name, " -path "))) {
12879             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12880             opt->textValue = p+7;
12881             opt->type = TextBox; // PathName;
12882         } else if(p = strstr(opt->name, " -check ")) {
12883             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12884             opt->value = (def != 0);
12885             opt->type = CheckBox;
12886         } else if(p = strstr(opt->name, " -combo ")) {
12887             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12888             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12889             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12890             opt->value = n = 0;
12891             while(q = StrStr(q, " /// ")) {
12892                 n++; *q = 0;    // count choices, and null-terminate each of them
12893                 q += 5;
12894                 if(*q == '*') { // remember default, which is marked with * prefix
12895                     q++;
12896                     opt->value = n;
12897                 }
12898                 cps->comboList[cps->comboCnt++] = q;
12899             }
12900             cps->comboList[cps->comboCnt++] = NULL;
12901             opt->max = n + 1;
12902             opt->type = ComboBox;
12903         } else if(p = strstr(opt->name, " -button")) {
12904             opt->type = Button;
12905         } else if(p = strstr(opt->name, " -save")) {
12906             opt->type = SaveButton;
12907         } else return FALSE;
12908         *p = 0; // terminate option name
12909         // now look if the command-line options define a setting for this engine option.
12910         if(cps->optionSettings && cps->optionSettings[0])
12911             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12912         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12913                 sprintf(buf, "option %s", p);
12914                 if(p = strstr(buf, ",")) *p = 0;
12915                 strcat(buf, "\n");
12916                 SendToProgram(buf, cps);
12917         }
12918         return TRUE;
12919 }
12920
12921 void
12922 FeatureDone(cps, val)
12923      ChessProgramState* cps;
12924      int val;
12925 {
12926   DelayedEventCallback cb = GetDelayedEvent();
12927   if ((cb == InitBackEnd3 && cps == &first) ||
12928       (cb == TwoMachinesEventIfReady && cps == &second)) {
12929     CancelDelayedEvent();
12930     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12931   }
12932   cps->initDone = val;
12933 }
12934
12935 /* Parse feature command from engine */
12936 void
12937 ParseFeatures(args, cps)
12938      char* args;
12939      ChessProgramState *cps;  
12940 {
12941   char *p = args;
12942   char *q;
12943   int val;
12944   char buf[MSG_SIZ];
12945
12946   for (;;) {
12947     while (*p == ' ') p++;
12948     if (*p == NULLCHAR) return;
12949
12950     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12951     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12952     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12953     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12954     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12955     if (BoolFeature(&p, "reuse", &val, cps)) {
12956       /* Engine can disable reuse, but can't enable it if user said no */
12957       if (!val) cps->reuse = FALSE;
12958       continue;
12959     }
12960     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12961     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12962       if (gameMode == TwoMachinesPlay) {
12963         DisplayTwoMachinesTitle();
12964       } else {
12965         DisplayTitle("");
12966       }
12967       continue;
12968     }
12969     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12970     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12971     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12972     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12973     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12974     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12975     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12976     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12977     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12978     if (IntFeature(&p, "done", &val, cps)) {
12979       FeatureDone(cps, val);
12980       continue;
12981     }
12982     /* Added by Tord: */
12983     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12984     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12985     /* End of additions by Tord */
12986
12987     /* [HGM] added features: */
12988     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12989     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12990     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12991     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12992     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12993     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12994     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12995         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12996             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12997             SendToProgram(buf, cps);
12998             continue;
12999         }
13000         if(cps->nrOptions >= MAX_OPTIONS) {
13001             cps->nrOptions--;
13002             sprintf(buf, "%s engine has too many options\n", cps->which);
13003             DisplayError(buf, 0);
13004         }
13005         continue;
13006     }
13007     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13008     /* End of additions by HGM */
13009
13010     /* unknown feature: complain and skip */
13011     q = p;
13012     while (*q && *q != '=') q++;
13013     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13014     SendToProgram(buf, cps);
13015     p = q;
13016     if (*p == '=') {
13017       p++;
13018       if (*p == '\"') {
13019         p++;
13020         while (*p && *p != '\"') p++;
13021         if (*p == '\"') p++;
13022       } else {
13023         while (*p && *p != ' ') p++;
13024       }
13025     }
13026   }
13027
13028 }
13029
13030 void
13031 PeriodicUpdatesEvent(newState)
13032      int newState;
13033 {
13034     if (newState == appData.periodicUpdates)
13035       return;
13036
13037     appData.periodicUpdates=newState;
13038
13039     /* Display type changes, so update it now */
13040 //    DisplayAnalysis();
13041
13042     /* Get the ball rolling again... */
13043     if (newState) {
13044         AnalysisPeriodicEvent(1);
13045         StartAnalysisClock();
13046     }
13047 }
13048
13049 void
13050 PonderNextMoveEvent(newState)
13051      int newState;
13052 {
13053     if (newState == appData.ponderNextMove) return;
13054     if (gameMode == EditPosition) EditPositionDone(TRUE);
13055     if (newState) {
13056         SendToProgram("hard\n", &first);
13057         if (gameMode == TwoMachinesPlay) {
13058             SendToProgram("hard\n", &second);
13059         }
13060     } else {
13061         SendToProgram("easy\n", &first);
13062         thinkOutput[0] = NULLCHAR;
13063         if (gameMode == TwoMachinesPlay) {
13064             SendToProgram("easy\n", &second);
13065         }
13066     }
13067     appData.ponderNextMove = newState;
13068 }
13069
13070 void
13071 NewSettingEvent(option, command, value)
13072      char *command;
13073      int option, value;
13074 {
13075     char buf[MSG_SIZ];
13076
13077     if (gameMode == EditPosition) EditPositionDone(TRUE);
13078     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13079     SendToProgram(buf, &first);
13080     if (gameMode == TwoMachinesPlay) {
13081         SendToProgram(buf, &second);
13082     }
13083 }
13084
13085 void
13086 ShowThinkingEvent()
13087 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13088 {
13089     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13090     int newState = appData.showThinking
13091         // [HGM] thinking: other features now need thinking output as well
13092         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13093     
13094     if (oldState == newState) return;
13095     oldState = newState;
13096     if (gameMode == EditPosition) EditPositionDone(TRUE);
13097     if (oldState) {
13098         SendToProgram("post\n", &first);
13099         if (gameMode == TwoMachinesPlay) {
13100             SendToProgram("post\n", &second);
13101         }
13102     } else {
13103         SendToProgram("nopost\n", &first);
13104         thinkOutput[0] = NULLCHAR;
13105         if (gameMode == TwoMachinesPlay) {
13106             SendToProgram("nopost\n", &second);
13107         }
13108     }
13109 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13110 }
13111
13112 void
13113 AskQuestionEvent(title, question, replyPrefix, which)
13114      char *title; char *question; char *replyPrefix; char *which;
13115 {
13116   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13117   if (pr == NoProc) return;
13118   AskQuestion(title, question, replyPrefix, pr);
13119 }
13120
13121 void
13122 DisplayMove(moveNumber)
13123      int moveNumber;
13124 {
13125     char message[MSG_SIZ];
13126     char res[MSG_SIZ];
13127     char cpThinkOutput[MSG_SIZ];
13128
13129     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13130     
13131     if (moveNumber == forwardMostMove - 1 || 
13132         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13133
13134         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13135
13136         if (strchr(cpThinkOutput, '\n')) {
13137             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13138         }
13139     } else {
13140         *cpThinkOutput = NULLCHAR;
13141     }
13142
13143     /* [AS] Hide thinking from human user */
13144     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13145         *cpThinkOutput = NULLCHAR;
13146         if( thinkOutput[0] != NULLCHAR ) {
13147             int i;
13148
13149             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13150                 cpThinkOutput[i] = '.';
13151             }
13152             cpThinkOutput[i] = NULLCHAR;
13153             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13154         }
13155     }
13156
13157     if (moveNumber == forwardMostMove - 1 &&
13158         gameInfo.resultDetails != NULL) {
13159         if (gameInfo.resultDetails[0] == NULLCHAR) {
13160             sprintf(res, " %s", PGNResult(gameInfo.result));
13161         } else {
13162             sprintf(res, " {%s} %s",
13163                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13164         }
13165     } else {
13166         res[0] = NULLCHAR;
13167     }
13168
13169     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13170         DisplayMessage(res, cpThinkOutput);
13171     } else {
13172         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13173                 WhiteOnMove(moveNumber) ? " " : ".. ",
13174                 parseList[moveNumber], res);
13175         DisplayMessage(message, cpThinkOutput);
13176     }
13177 }
13178
13179 void
13180 DisplayComment(moveNumber, text)
13181      int moveNumber;
13182      char *text;
13183 {
13184     char title[MSG_SIZ];
13185     char buf[8000]; // comment can be long!
13186     int score, depth;
13187     
13188     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13189       strcpy(title, "Comment");
13190     } else {
13191       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13192               WhiteOnMove(moveNumber) ? " " : ".. ",
13193               parseList[moveNumber]);
13194     }
13195     // [HGM] PV info: display PV info together with (or as) comment
13196     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13197       if(text == NULL) text = "";                                           
13198       score = pvInfoList[moveNumber].score;
13199       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13200               depth, (pvInfoList[moveNumber].time+50)/100, text);
13201       text = buf;
13202     }
13203     if (text != NULL && (appData.autoDisplayComment || commentUp))
13204         CommentPopUp(title, text);
13205 }
13206
13207 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13208  * might be busy thinking or pondering.  It can be omitted if your
13209  * gnuchess is configured to stop thinking immediately on any user
13210  * input.  However, that gnuchess feature depends on the FIONREAD
13211  * ioctl, which does not work properly on some flavors of Unix.
13212  */
13213 void
13214 Attention(cps)
13215      ChessProgramState *cps;
13216 {
13217 #if ATTENTION
13218     if (!cps->useSigint) return;
13219     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13220     switch (gameMode) {
13221       case MachinePlaysWhite:
13222       case MachinePlaysBlack:
13223       case TwoMachinesPlay:
13224       case IcsPlayingWhite:
13225       case IcsPlayingBlack:
13226       case AnalyzeMode:
13227       case AnalyzeFile:
13228         /* Skip if we know it isn't thinking */
13229         if (!cps->maybeThinking) return;
13230         if (appData.debugMode)
13231           fprintf(debugFP, "Interrupting %s\n", cps->which);
13232         InterruptChildProcess(cps->pr);
13233         cps->maybeThinking = FALSE;
13234         break;
13235       default:
13236         break;
13237     }
13238 #endif /*ATTENTION*/
13239 }
13240
13241 int
13242 CheckFlags()
13243 {
13244     if (whiteTimeRemaining <= 0) {
13245         if (!whiteFlag) {
13246             whiteFlag = TRUE;
13247             if (appData.icsActive) {
13248                 if (appData.autoCallFlag &&
13249                     gameMode == IcsPlayingBlack && !blackFlag) {
13250                   SendToICS(ics_prefix);
13251                   SendToICS("flag\n");
13252                 }
13253             } else {
13254                 if (blackFlag) {
13255                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13256                 } else {
13257                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13258                     if (appData.autoCallFlag) {
13259                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13260                         return TRUE;
13261                     }
13262                 }
13263             }
13264         }
13265     }
13266     if (blackTimeRemaining <= 0) {
13267         if (!blackFlag) {
13268             blackFlag = TRUE;
13269             if (appData.icsActive) {
13270                 if (appData.autoCallFlag &&
13271                     gameMode == IcsPlayingWhite && !whiteFlag) {
13272                   SendToICS(ics_prefix);
13273                   SendToICS("flag\n");
13274                 }
13275             } else {
13276                 if (whiteFlag) {
13277                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13278                 } else {
13279                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13280                     if (appData.autoCallFlag) {
13281                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13282                         return TRUE;
13283                     }
13284                 }
13285             }
13286         }
13287     }
13288     return FALSE;
13289 }
13290
13291 void
13292 CheckTimeControl()
13293 {
13294     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13295         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13296
13297     /*
13298      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13299      */
13300     if ( !WhiteOnMove(forwardMostMove) )
13301         /* White made time control */
13302         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13303         /* [HGM] time odds: correct new time quota for time odds! */
13304                                             / WhitePlayer()->timeOdds;
13305       else
13306         /* Black made time control */
13307         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13308                                             / WhitePlayer()->other->timeOdds;
13309 }
13310
13311 void
13312 DisplayBothClocks()
13313 {
13314     int wom = gameMode == EditPosition ?
13315       !blackPlaysFirst : WhiteOnMove(currentMove);
13316     DisplayWhiteClock(whiteTimeRemaining, wom);
13317     DisplayBlackClock(blackTimeRemaining, !wom);
13318 }
13319
13320
13321 /* Timekeeping seems to be a portability nightmare.  I think everyone
13322    has ftime(), but I'm really not sure, so I'm including some ifdefs
13323    to use other calls if you don't.  Clocks will be less accurate if
13324    you have neither ftime nor gettimeofday.
13325 */
13326
13327 /* VS 2008 requires the #include outside of the function */
13328 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13329 #include <sys/timeb.h>
13330 #endif
13331
13332 /* Get the current time as a TimeMark */
13333 void
13334 GetTimeMark(tm)
13335      TimeMark *tm;
13336 {
13337 #if HAVE_GETTIMEOFDAY
13338
13339     struct timeval timeVal;
13340     struct timezone timeZone;
13341
13342     gettimeofday(&timeVal, &timeZone);
13343     tm->sec = (long) timeVal.tv_sec; 
13344     tm->ms = (int) (timeVal.tv_usec / 1000L);
13345
13346 #else /*!HAVE_GETTIMEOFDAY*/
13347 #if HAVE_FTIME
13348
13349 // include <sys/timeb.h> / moved to just above start of function
13350     struct timeb timeB;
13351
13352     ftime(&timeB);
13353     tm->sec = (long) timeB.time;
13354     tm->ms = (int) timeB.millitm;
13355
13356 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13357     tm->sec = (long) time(NULL);
13358     tm->ms = 0;
13359 #endif
13360 #endif
13361 }
13362
13363 /* Return the difference in milliseconds between two
13364    time marks.  We assume the difference will fit in a long!
13365 */
13366 long
13367 SubtractTimeMarks(tm2, tm1)
13368      TimeMark *tm2, *tm1;
13369 {
13370     return 1000L*(tm2->sec - tm1->sec) +
13371            (long) (tm2->ms - tm1->ms);
13372 }
13373
13374
13375 /*
13376  * Code to manage the game clocks.
13377  *
13378  * In tournament play, black starts the clock and then white makes a move.
13379  * We give the human user a slight advantage if he is playing white---the
13380  * clocks don't run until he makes his first move, so it takes zero time.
13381  * Also, we don't account for network lag, so we could get out of sync
13382  * with GNU Chess's clock -- but then, referees are always right.  
13383  */
13384
13385 static TimeMark tickStartTM;
13386 static long intendedTickLength;
13387
13388 long
13389 NextTickLength(timeRemaining)
13390      long timeRemaining;
13391 {
13392     long nominalTickLength, nextTickLength;
13393
13394     if (timeRemaining > 0L && timeRemaining <= 10000L)
13395       nominalTickLength = 100L;
13396     else
13397       nominalTickLength = 1000L;
13398     nextTickLength = timeRemaining % nominalTickLength;
13399     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13400
13401     return nextTickLength;
13402 }
13403
13404 /* Adjust clock one minute up or down */
13405 void
13406 AdjustClock(Boolean which, int dir)
13407 {
13408     if(which) blackTimeRemaining += 60000*dir;
13409     else      whiteTimeRemaining += 60000*dir;
13410     DisplayBothClocks();
13411 }
13412
13413 /* Stop clocks and reset to a fresh time control */
13414 void
13415 ResetClocks() 
13416 {
13417     (void) StopClockTimer();
13418     if (appData.icsActive) {
13419         whiteTimeRemaining = blackTimeRemaining = 0;
13420     } else if (searchTime) {
13421         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13422         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13423     } else { /* [HGM] correct new time quote for time odds */
13424         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13425         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13426     }
13427     if (whiteFlag || blackFlag) {
13428         DisplayTitle("");
13429         whiteFlag = blackFlag = FALSE;
13430     }
13431     DisplayBothClocks();
13432 }
13433
13434 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13435
13436 /* Decrement running clock by amount of time that has passed */
13437 void
13438 DecrementClocks()
13439 {
13440     long timeRemaining;
13441     long lastTickLength, fudge;
13442     TimeMark now;
13443
13444     if (!appData.clockMode) return;
13445     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13446         
13447     GetTimeMark(&now);
13448
13449     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13450
13451     /* Fudge if we woke up a little too soon */
13452     fudge = intendedTickLength - lastTickLength;
13453     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13454
13455     if (WhiteOnMove(forwardMostMove)) {
13456         if(whiteNPS >= 0) lastTickLength = 0;
13457         timeRemaining = whiteTimeRemaining -= lastTickLength;
13458         DisplayWhiteClock(whiteTimeRemaining - fudge,
13459                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13460     } else {
13461         if(blackNPS >= 0) lastTickLength = 0;
13462         timeRemaining = blackTimeRemaining -= lastTickLength;
13463         DisplayBlackClock(blackTimeRemaining - fudge,
13464                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13465     }
13466
13467     if (CheckFlags()) return;
13468         
13469     tickStartTM = now;
13470     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13471     StartClockTimer(intendedTickLength);
13472
13473     /* if the time remaining has fallen below the alarm threshold, sound the
13474      * alarm. if the alarm has sounded and (due to a takeback or time control
13475      * with increment) the time remaining has increased to a level above the
13476      * threshold, reset the alarm so it can sound again. 
13477      */
13478     
13479     if (appData.icsActive && appData.icsAlarm) {
13480
13481         /* make sure we are dealing with the user's clock */
13482         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13483                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13484            )) return;
13485
13486         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13487             alarmSounded = FALSE;
13488         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13489             PlayAlarmSound();
13490             alarmSounded = TRUE;
13491         }
13492     }
13493 }
13494
13495
13496 /* A player has just moved, so stop the previously running
13497    clock and (if in clock mode) start the other one.
13498    We redisplay both clocks in case we're in ICS mode, because
13499    ICS gives us an update to both clocks after every move.
13500    Note that this routine is called *after* forwardMostMove
13501    is updated, so the last fractional tick must be subtracted
13502    from the color that is *not* on move now.
13503 */
13504 void
13505 SwitchClocks()
13506 {
13507     long lastTickLength;
13508     TimeMark now;
13509     int flagged = FALSE;
13510
13511     GetTimeMark(&now);
13512
13513     if (StopClockTimer() && appData.clockMode) {
13514         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13515         if (WhiteOnMove(forwardMostMove)) {
13516             if(blackNPS >= 0) lastTickLength = 0;
13517             blackTimeRemaining -= lastTickLength;
13518            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13519 //         if(pvInfoList[forwardMostMove-1].time == -1)
13520                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13521                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13522         } else {
13523            if(whiteNPS >= 0) lastTickLength = 0;
13524            whiteTimeRemaining -= lastTickLength;
13525            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13526 //         if(pvInfoList[forwardMostMove-1].time == -1)
13527                  pvInfoList[forwardMostMove-1].time = 
13528                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13529         }
13530         flagged = CheckFlags();
13531     }
13532     CheckTimeControl();
13533
13534     if (flagged || !appData.clockMode) return;
13535
13536     switch (gameMode) {
13537       case MachinePlaysBlack:
13538       case MachinePlaysWhite:
13539       case BeginningOfGame:
13540         if (pausing) return;
13541         break;
13542
13543       case EditGame:
13544       case PlayFromGameFile:
13545       case IcsExamining:
13546         return;
13547
13548       default:
13549         break;
13550     }
13551
13552     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13553         if(WhiteOnMove(forwardMostMove))
13554              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13555         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13556     }
13557
13558     tickStartTM = now;
13559     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13560       whiteTimeRemaining : blackTimeRemaining);
13561     StartClockTimer(intendedTickLength);
13562 }
13563         
13564
13565 /* Stop both clocks */
13566 void
13567 StopClocks()
13568 {       
13569     long lastTickLength;
13570     TimeMark now;
13571
13572     if (!StopClockTimer()) return;
13573     if (!appData.clockMode) return;
13574
13575     GetTimeMark(&now);
13576
13577     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13578     if (WhiteOnMove(forwardMostMove)) {
13579         if(whiteNPS >= 0) lastTickLength = 0;
13580         whiteTimeRemaining -= lastTickLength;
13581         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13582     } else {
13583         if(blackNPS >= 0) lastTickLength = 0;
13584         blackTimeRemaining -= lastTickLength;
13585         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13586     }
13587     CheckFlags();
13588 }
13589         
13590 /* Start clock of player on move.  Time may have been reset, so
13591    if clock is already running, stop and restart it. */
13592 void
13593 StartClocks()
13594 {
13595     (void) StopClockTimer(); /* in case it was running already */
13596     DisplayBothClocks();
13597     if (CheckFlags()) return;
13598
13599     if (!appData.clockMode) return;
13600     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13601
13602     GetTimeMark(&tickStartTM);
13603     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13604       whiteTimeRemaining : blackTimeRemaining);
13605
13606    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13607     whiteNPS = blackNPS = -1; 
13608     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13609        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13610         whiteNPS = first.nps;
13611     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13612        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13613         blackNPS = first.nps;
13614     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13615         whiteNPS = second.nps;
13616     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13617         blackNPS = second.nps;
13618     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13619
13620     StartClockTimer(intendedTickLength);
13621 }
13622
13623 char *
13624 TimeString(ms)
13625      long ms;
13626 {
13627     long second, minute, hour, day;
13628     char *sign = "";
13629     static char buf[32];
13630     
13631     if (ms > 0 && ms <= 9900) {
13632       /* convert milliseconds to tenths, rounding up */
13633       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13634
13635       sprintf(buf, " %03.1f ", tenths/10.0);
13636       return buf;
13637     }
13638
13639     /* convert milliseconds to seconds, rounding up */
13640     /* use floating point to avoid strangeness of integer division
13641        with negative dividends on many machines */
13642     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13643
13644     if (second < 0) {
13645         sign = "-";
13646         second = -second;
13647     }
13648     
13649     day = second / (60 * 60 * 24);
13650     second = second % (60 * 60 * 24);
13651     hour = second / (60 * 60);
13652     second = second % (60 * 60);
13653     minute = second / 60;
13654     second = second % 60;
13655     
13656     if (day > 0)
13657       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13658               sign, day, hour, minute, second);
13659     else if (hour > 0)
13660       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13661     else
13662       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13663     
13664     return buf;
13665 }
13666
13667
13668 /*
13669  * This is necessary because some C libraries aren't ANSI C compliant yet.
13670  */
13671 char *
13672 StrStr(string, match)
13673      char *string, *match;
13674 {
13675     int i, length;
13676     
13677     length = strlen(match);
13678     
13679     for (i = strlen(string) - length; i >= 0; i--, string++)
13680       if (!strncmp(match, string, length))
13681         return string;
13682     
13683     return NULL;
13684 }
13685
13686 char *
13687 StrCaseStr(string, match)
13688      char *string, *match;
13689 {
13690     int i, j, length;
13691     
13692     length = strlen(match);
13693     
13694     for (i = strlen(string) - length; i >= 0; i--, string++) {
13695         for (j = 0; j < length; j++) {
13696             if (ToLower(match[j]) != ToLower(string[j]))
13697               break;
13698         }
13699         if (j == length) return string;
13700     }
13701
13702     return NULL;
13703 }
13704
13705 #ifndef _amigados
13706 int
13707 StrCaseCmp(s1, s2)
13708      char *s1, *s2;
13709 {
13710     char c1, c2;
13711     
13712     for (;;) {
13713         c1 = ToLower(*s1++);
13714         c2 = ToLower(*s2++);
13715         if (c1 > c2) return 1;
13716         if (c1 < c2) return -1;
13717         if (c1 == NULLCHAR) return 0;
13718     }
13719 }
13720
13721
13722 int
13723 ToLower(c)
13724      int c;
13725 {
13726     return isupper(c) ? tolower(c) : c;
13727 }
13728
13729
13730 int
13731 ToUpper(c)
13732      int c;
13733 {
13734     return islower(c) ? toupper(c) : c;
13735 }
13736 #endif /* !_amigados    */
13737
13738 char *
13739 StrSave(s)
13740      char *s;
13741 {
13742     char *ret;
13743
13744     if ((ret = (char *) malloc(strlen(s) + 1))) {
13745         strcpy(ret, s);
13746     }
13747     return ret;
13748 }
13749
13750 char *
13751 StrSavePtr(s, savePtr)
13752      char *s, **savePtr;
13753 {
13754     if (*savePtr) {
13755         free(*savePtr);
13756     }
13757     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13758         strcpy(*savePtr, s);
13759     }
13760     return(*savePtr);
13761 }
13762
13763 char *
13764 PGNDate()
13765 {
13766     time_t clock;
13767     struct tm *tm;
13768     char buf[MSG_SIZ];
13769
13770     clock = time((time_t *)NULL);
13771     tm = localtime(&clock);
13772     sprintf(buf, "%04d.%02d.%02d",
13773             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13774     return StrSave(buf);
13775 }
13776
13777
13778 char *
13779 PositionToFEN(move, overrideCastling)
13780      int move;
13781      char *overrideCastling;
13782 {
13783     int i, j, fromX, fromY, toX, toY;
13784     int whiteToPlay;
13785     char buf[128];
13786     char *p, *q;
13787     int emptycount;
13788     ChessSquare piece;
13789
13790     whiteToPlay = (gameMode == EditPosition) ?
13791       !blackPlaysFirst : (move % 2 == 0);
13792     p = buf;
13793
13794     /* Piece placement data */
13795     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13796         emptycount = 0;
13797         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13798             if (boards[move][i][j] == EmptySquare) {
13799                 emptycount++;
13800             } else { ChessSquare piece = boards[move][i][j];
13801                 if (emptycount > 0) {
13802                     if(emptycount<10) /* [HGM] can be >= 10 */
13803                         *p++ = '0' + emptycount;
13804                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13805                     emptycount = 0;
13806                 }
13807                 if(PieceToChar(piece) == '+') {
13808                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13809                     *p++ = '+';
13810                     piece = (ChessSquare)(DEMOTED piece);
13811                 } 
13812                 *p++ = PieceToChar(piece);
13813                 if(p[-1] == '~') {
13814                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13815                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13816                     *p++ = '~';
13817                 }
13818             }
13819         }
13820         if (emptycount > 0) {
13821             if(emptycount<10) /* [HGM] can be >= 10 */
13822                 *p++ = '0' + emptycount;
13823             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13824             emptycount = 0;
13825         }
13826         *p++ = '/';
13827     }
13828     *(p - 1) = ' ';
13829
13830     /* [HGM] print Crazyhouse or Shogi holdings */
13831     if( gameInfo.holdingsWidth ) {
13832         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13833         q = p;
13834         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13835             piece = boards[move][i][BOARD_WIDTH-1];
13836             if( piece != EmptySquare )
13837               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13838                   *p++ = PieceToChar(piece);
13839         }
13840         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13841             piece = boards[move][BOARD_HEIGHT-i-1][0];
13842             if( piece != EmptySquare )
13843               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13844                   *p++ = PieceToChar(piece);
13845         }
13846
13847         if( q == p ) *p++ = '-';
13848         *p++ = ']';
13849         *p++ = ' ';
13850     }
13851
13852     /* Active color */
13853     *p++ = whiteToPlay ? 'w' : 'b';
13854     *p++ = ' ';
13855
13856   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13857     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13858   } else {
13859   if(nrCastlingRights) {
13860      q = p;
13861      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13862        /* [HGM] write directly from rights */
13863            if(boards[move][CASTLING][2] != NoRights &&
13864               boards[move][CASTLING][0] != NoRights   )
13865                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13866            if(boards[move][CASTLING][2] != NoRights &&
13867               boards[move][CASTLING][1] != NoRights   )
13868                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13869            if(boards[move][CASTLING][5] != NoRights &&
13870               boards[move][CASTLING][3] != NoRights   )
13871                 *p++ = boards[move][CASTLING][3] + AAA;
13872            if(boards[move][CASTLING][5] != NoRights &&
13873               boards[move][CASTLING][4] != NoRights   )
13874                 *p++ = boards[move][CASTLING][4] + AAA;
13875      } else {
13876
13877         /* [HGM] write true castling rights */
13878         if( nrCastlingRights == 6 ) {
13879             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13880                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13881             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13882                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13883             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13884                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13885             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13886                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13887         }
13888      }
13889      if (q == p) *p++ = '-'; /* No castling rights */
13890      *p++ = ' ';
13891   }
13892
13893   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13894      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13895     /* En passant target square */
13896     if (move > backwardMostMove) {
13897         fromX = moveList[move - 1][0] - AAA;
13898         fromY = moveList[move - 1][1] - ONE;
13899         toX = moveList[move - 1][2] - AAA;
13900         toY = moveList[move - 1][3] - ONE;
13901         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13902             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13903             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13904             fromX == toX) {
13905             /* 2-square pawn move just happened */
13906             *p++ = toX + AAA;
13907             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13908         } else {
13909             *p++ = '-';
13910         }
13911     } else if(move == backwardMostMove) {
13912         // [HGM] perhaps we should always do it like this, and forget the above?
13913         if((signed char)boards[move][EP_STATUS] >= 0) {
13914             *p++ = boards[move][EP_STATUS] + AAA;
13915             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13916         } else {
13917             *p++ = '-';
13918         }
13919     } else {
13920         *p++ = '-';
13921     }
13922     *p++ = ' ';
13923   }
13924   }
13925
13926     /* [HGM] find reversible plies */
13927     {   int i = 0, j=move;
13928
13929         if (appData.debugMode) { int k;
13930             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13931             for(k=backwardMostMove; k<=forwardMostMove; k++)
13932                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13933
13934         }
13935
13936         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13937         if( j == backwardMostMove ) i += initialRulePlies;
13938         sprintf(p, "%d ", i);
13939         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13940     }
13941     /* Fullmove number */
13942     sprintf(p, "%d", (move / 2) + 1);
13943     
13944     return StrSave(buf);
13945 }
13946
13947 Boolean
13948 ParseFEN(board, blackPlaysFirst, fen)
13949     Board board;
13950      int *blackPlaysFirst;
13951      char *fen;
13952 {
13953     int i, j;
13954     char *p;
13955     int emptycount;
13956     ChessSquare piece;
13957
13958     p = fen;
13959
13960     /* [HGM] by default clear Crazyhouse holdings, if present */
13961     if(gameInfo.holdingsWidth) {
13962        for(i=0; i<BOARD_HEIGHT; i++) {
13963            board[i][0]             = EmptySquare; /* black holdings */
13964            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13965            board[i][1]             = (ChessSquare) 0; /* black counts */
13966            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13967        }
13968     }
13969
13970     /* Piece placement data */
13971     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13972         j = 0;
13973         for (;;) {
13974             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13975                 if (*p == '/') p++;
13976                 emptycount = gameInfo.boardWidth - j;
13977                 while (emptycount--)
13978                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13979                 break;
13980 #if(BOARD_FILES >= 10)
13981             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13982                 p++; emptycount=10;
13983                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13984                 while (emptycount--)
13985                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13986 #endif
13987             } else if (isdigit(*p)) {
13988                 emptycount = *p++ - '0';
13989                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13990                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13991                 while (emptycount--)
13992                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13993             } else if (*p == '+' || isalpha(*p)) {
13994                 if (j >= gameInfo.boardWidth) return FALSE;
13995                 if(*p=='+') {
13996                     piece = CharToPiece(*++p);
13997                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13998                     piece = (ChessSquare) (PROMOTED piece ); p++;
13999                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14000                 } else piece = CharToPiece(*p++);
14001
14002                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14003                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14004                     piece = (ChessSquare) (PROMOTED piece);
14005                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14006                     p++;
14007                 }
14008                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14009             } else {
14010                 return FALSE;
14011             }
14012         }
14013     }
14014     while (*p == '/' || *p == ' ') p++;
14015
14016     /* [HGM] look for Crazyhouse holdings here */
14017     while(*p==' ') p++;
14018     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14019         if(*p == '[') p++;
14020         if(*p == '-' ) *p++; /* empty holdings */ else {
14021             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14022             /* if we would allow FEN reading to set board size, we would   */
14023             /* have to add holdings and shift the board read so far here   */
14024             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14025                 *p++;
14026                 if((int) piece >= (int) BlackPawn ) {
14027                     i = (int)piece - (int)BlackPawn;
14028                     i = PieceToNumber((ChessSquare)i);
14029                     if( i >= gameInfo.holdingsSize ) return FALSE;
14030                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14031                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14032                 } else {
14033                     i = (int)piece - (int)WhitePawn;
14034                     i = PieceToNumber((ChessSquare)i);
14035                     if( i >= gameInfo.holdingsSize ) return FALSE;
14036                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14037                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14038                 }
14039             }
14040         }
14041         if(*p == ']') *p++;
14042     }
14043
14044     while(*p == ' ') p++;
14045
14046     /* Active color */
14047     switch (*p++) {
14048       case 'w':
14049         *blackPlaysFirst = FALSE;
14050         break;
14051       case 'b': 
14052         *blackPlaysFirst = TRUE;
14053         break;
14054       default:
14055         return FALSE;
14056     }
14057
14058     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14059     /* return the extra info in global variiables             */
14060
14061     /* set defaults in case FEN is incomplete */
14062     board[EP_STATUS] = EP_UNKNOWN;
14063     for(i=0; i<nrCastlingRights; i++ ) {
14064         board[CASTLING][i] =
14065             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14066     }   /* assume possible unless obviously impossible */
14067     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14068     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14069     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14070     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14071     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14072     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14073     FENrulePlies = 0;
14074
14075     while(*p==' ') p++;
14076     if(nrCastlingRights) {
14077       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14078           /* castling indicator present, so default becomes no castlings */
14079           for(i=0; i<nrCastlingRights; i++ ) {
14080                  board[CASTLING][i] = NoRights;
14081           }
14082       }
14083       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14084              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14085              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14086              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14087         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14088
14089         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14090             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14091             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14092         }
14093         switch(c) {
14094           case'K':
14095               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14096               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14097               board[CASTLING][2] = whiteKingFile;
14098               break;
14099           case'Q':
14100               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14101               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14102               board[CASTLING][2] = whiteKingFile;
14103               break;
14104           case'k':
14105               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14106               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14107               board[CASTLING][5] = blackKingFile;
14108               break;
14109           case'q':
14110               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14111               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14112               board[CASTLING][5] = blackKingFile;
14113           case '-':
14114               break;
14115           default: /* FRC castlings */
14116               if(c >= 'a') { /* black rights */
14117                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14118                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14119                   if(i == BOARD_RGHT) break;
14120                   board[CASTLING][5] = i;
14121                   c -= AAA;
14122                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14123                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14124                   if(c > i)
14125                       board[CASTLING][3] = c;
14126                   else
14127                       board[CASTLING][4] = c;
14128               } else { /* white rights */
14129                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14130                     if(board[0][i] == WhiteKing) break;
14131                   if(i == BOARD_RGHT) break;
14132                   board[CASTLING][2] = i;
14133                   c -= AAA - 'a' + 'A';
14134                   if(board[0][c] >= WhiteKing) break;
14135                   if(c > i)
14136                       board[CASTLING][0] = c;
14137                   else
14138                       board[CASTLING][1] = c;
14139               }
14140         }
14141       }
14142     if (appData.debugMode) {
14143         fprintf(debugFP, "FEN castling rights:");
14144         for(i=0; i<nrCastlingRights; i++)
14145         fprintf(debugFP, " %d", board[CASTLING][i]);
14146         fprintf(debugFP, "\n");
14147     }
14148
14149       while(*p==' ') p++;
14150     }
14151
14152     /* read e.p. field in games that know e.p. capture */
14153     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14154        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14155       if(*p=='-') {
14156         p++; board[EP_STATUS] = EP_NONE;
14157       } else {
14158          char c = *p++ - AAA;
14159
14160          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14161          if(*p >= '0' && *p <='9') *p++;
14162          board[EP_STATUS] = c;
14163       }
14164     }
14165
14166
14167     if(sscanf(p, "%d", &i) == 1) {
14168         FENrulePlies = i; /* 50-move ply counter */
14169         /* (The move number is still ignored)    */
14170     }
14171
14172     return TRUE;
14173 }
14174       
14175 void
14176 EditPositionPasteFEN(char *fen)
14177 {
14178   if (fen != NULL) {
14179     Board initial_position;
14180
14181     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14182       DisplayError(_("Bad FEN position in clipboard"), 0);
14183       return ;
14184     } else {
14185       int savedBlackPlaysFirst = blackPlaysFirst;
14186       EditPositionEvent();
14187       blackPlaysFirst = savedBlackPlaysFirst;
14188       CopyBoard(boards[0], initial_position);
14189       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14190       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14191       DisplayBothClocks();
14192       DrawPosition(FALSE, boards[currentMove]);
14193     }
14194   }
14195 }
14196
14197 static char cseq[12] = "\\   ";
14198
14199 Boolean set_cont_sequence(char *new_seq)
14200 {
14201     int len;
14202     Boolean ret;
14203
14204     // handle bad attempts to set the sequence
14205         if (!new_seq)
14206                 return 0; // acceptable error - no debug
14207
14208     len = strlen(new_seq);
14209     ret = (len > 0) && (len < sizeof(cseq));
14210     if (ret)
14211         strcpy(cseq, new_seq);
14212     else if (appData.debugMode)
14213         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14214     return ret;
14215 }
14216
14217 /*
14218     reformat a source message so words don't cross the width boundary.  internal
14219     newlines are not removed.  returns the wrapped size (no null character unless
14220     included in source message).  If dest is NULL, only calculate the size required
14221     for the dest buffer.  lp argument indicats line position upon entry, and it's
14222     passed back upon exit.
14223 */
14224 int wrap(char *dest, char *src, int count, int width, int *lp)
14225 {
14226     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14227
14228     cseq_len = strlen(cseq);
14229     old_line = line = *lp;
14230     ansi = len = clen = 0;
14231
14232     for (i=0; i < count; i++)
14233     {
14234         if (src[i] == '\033')
14235             ansi = 1;
14236
14237         // if we hit the width, back up
14238         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14239         {
14240             // store i & len in case the word is too long
14241             old_i = i, old_len = len;
14242
14243             // find the end of the last word
14244             while (i && src[i] != ' ' && src[i] != '\n')
14245             {
14246                 i--;
14247                 len--;
14248             }
14249
14250             // word too long?  restore i & len before splitting it
14251             if ((old_i-i+clen) >= width)
14252             {
14253                 i = old_i;
14254                 len = old_len;
14255             }
14256
14257             // extra space?
14258             if (i && src[i-1] == ' ')
14259                 len--;
14260
14261             if (src[i] != ' ' && src[i] != '\n')
14262             {
14263                 i--;
14264                 if (len)
14265                     len--;
14266             }
14267
14268             // now append the newline and continuation sequence
14269             if (dest)
14270                 dest[len] = '\n';
14271             len++;
14272             if (dest)
14273                 strncpy(dest+len, cseq, cseq_len);
14274             len += cseq_len;
14275             line = cseq_len;
14276             clen = cseq_len;
14277             continue;
14278         }
14279
14280         if (dest)
14281             dest[len] = src[i];
14282         len++;
14283         if (!ansi)
14284             line++;
14285         if (src[i] == '\n')
14286             line = 0;
14287         if (src[i] == 'm')
14288             ansi = 0;
14289     }
14290     if (dest && appData.debugMode)
14291     {
14292         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14293             count, width, line, len, *lp);
14294         show_bytes(debugFP, src, count);
14295         fprintf(debugFP, "\ndest: ");
14296         show_bytes(debugFP, dest, len);
14297         fprintf(debugFP, "\n");
14298     }
14299     *lp = dest ? line : old_line;
14300
14301     return len;
14302 }
14303
14304 // [HGM] vari: routines for shelving variations
14305
14306 void 
14307 PushTail(int firstMove, int lastMove)
14308 {
14309         int i, j, nrMoves = lastMove - firstMove;
14310
14311         if(appData.icsActive) { // only in local mode
14312                 forwardMostMove = currentMove; // mimic old ICS behavior
14313                 return;
14314         }
14315         if(storedGames >= MAX_VARIATIONS-1) return;
14316
14317         // push current tail of game on stack
14318         savedResult[storedGames] = gameInfo.result;
14319         savedDetails[storedGames] = gameInfo.resultDetails;
14320         gameInfo.resultDetails = NULL;
14321         savedFirst[storedGames] = firstMove;
14322         savedLast [storedGames] = lastMove;
14323         savedFramePtr[storedGames] = framePtr;
14324         framePtr -= nrMoves; // reserve space for the boards
14325         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14326             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14327             for(j=0; j<MOVE_LEN; j++)
14328                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14329             for(j=0; j<2*MOVE_LEN; j++)
14330                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14331             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14332             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14333             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14334             pvInfoList[firstMove+i-1].depth = 0;
14335             commentList[framePtr+i] = commentList[firstMove+i];
14336             commentList[firstMove+i] = NULL;
14337         }
14338
14339         storedGames++;
14340         forwardMostMove = currentMove; // truncte game so we can start variation
14341         if(storedGames == 1) GreyRevert(FALSE);
14342 }
14343
14344 Boolean
14345 PopTail(Boolean annotate)
14346 {
14347         int i, j, nrMoves;
14348         char buf[8000], moveBuf[20];
14349
14350         if(appData.icsActive) return FALSE; // only in local mode
14351         if(!storedGames) return FALSE; // sanity
14352
14353         storedGames--;
14354         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14355         nrMoves = savedLast[storedGames] - currentMove;
14356         if(annotate) {
14357                 int cnt = 10;
14358                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14359                 else strcpy(buf, "(");
14360                 for(i=currentMove; i<forwardMostMove; i++) {
14361                         if(WhiteOnMove(i))
14362                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14363                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14364                         strcat(buf, moveBuf);
14365                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14366                 }
14367                 strcat(buf, ")");
14368         }
14369         for(i=1; i<nrMoves; i++) { // copy last variation back
14370             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14371             for(j=0; j<MOVE_LEN; j++)
14372                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14373             for(j=0; j<2*MOVE_LEN; j++)
14374                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14375             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14376             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14377             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14378             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14379             commentList[currentMove+i] = commentList[framePtr+i];
14380             commentList[framePtr+i] = NULL;
14381         }
14382         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14383         framePtr = savedFramePtr[storedGames];
14384         gameInfo.result = savedResult[storedGames];
14385         if(gameInfo.resultDetails != NULL) {
14386             free(gameInfo.resultDetails);
14387       }
14388         gameInfo.resultDetails = savedDetails[storedGames];
14389         forwardMostMove = currentMove + nrMoves;
14390         if(storedGames == 0) GreyRevert(TRUE);
14391         return TRUE;
14392 }
14393
14394 void 
14395 CleanupTail()
14396 {       // remove all shelved variations
14397         int i;
14398         for(i=0; i<storedGames; i++) {
14399             if(savedDetails[i])
14400                 free(savedDetails[i]);
14401             savedDetails[i] = NULL;
14402         }
14403         for(i=framePtr; i<MAX_MOVES; i++) {
14404                 if(commentList[i]) free(commentList[i]);
14405                 commentList[i] = NULL;
14406         }
14407         framePtr = MAX_MOVES-1;
14408         storedGames = 0;
14409 }