Bugfix legality null move in parsing with -testLegality off
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char  initialRights[BOARD_FILES];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
449 int loadFlag = 0; 
450 int shuffleOpenings;
451 int mute; // mute all sounds
452
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
456 int storedGames = 0;
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
462
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
466
467 ChessSquare  FIDEArray[2][BOARD_FILES] = {
468     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471         BlackKing, BlackBishop, BlackKnight, BlackRook }
472 };
473
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackKing, BlackKnight, BlackRook }
479 };
480
481 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484     { BlackRook, BlackMan, BlackBishop, BlackQueen,
485         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
486 };
487
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492         BlackKing, BlackBishop, BlackKnight, BlackRook }
493 };
494
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
500 };
501
502
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
509 };
510
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
516 };
517
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
520         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
522         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
523 };
524
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
527         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
529         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
530 };
531
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
534         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
536         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
537 };
538
539 #ifdef GOTHIC
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
542         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
544         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !GOTHIC
547 #define GothicArray CapablancaArray
548 #endif // !GOTHIC
549
550 #ifdef FALCON
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
553         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
555         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !FALCON
558 #define FalconArray CapablancaArray
559 #endif // !FALCON
560
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
567
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
574 };
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
578
579
580 Board initialPosition;
581
582
583 /* Convert str to a rating. Checks for special cases of "----",
584
585    "++++", etc. Also strips ()'s */
586 int
587 string_to_rating(str)
588   char *str;
589 {
590   while(*str && !isdigit(*str)) ++str;
591   if (!*str)
592     return 0;   /* One of the special "no rating" cases */
593   else
594     return atoi(str);
595 }
596
597 void
598 ClearProgramStats()
599 {
600     /* Init programStats */
601     programStats.movelist[0] = 0;
602     programStats.depth = 0;
603     programStats.nr_moves = 0;
604     programStats.moves_left = 0;
605     programStats.nodes = 0;
606     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
607     programStats.score = 0;
608     programStats.got_only_move = 0;
609     programStats.got_fail = 0;
610     programStats.line_is_book = 0;
611 }
612
613 void
614 InitBackEnd1()
615 {
616     int matched, min, sec;
617
618     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
619
620     GetTimeMark(&programStartTime);
621     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
622
623     ClearProgramStats();
624     programStats.ok_to_send = 1;
625     programStats.seen_stat = 0;
626
627     /*
628      * Initialize game list
629      */
630     ListNew(&gameList);
631
632
633     /*
634      * Internet chess server status
635      */
636     if (appData.icsActive) {
637         appData.matchMode = FALSE;
638         appData.matchGames = 0;
639 #if ZIPPY       
640         appData.noChessProgram = !appData.zippyPlay;
641 #else
642         appData.zippyPlay = FALSE;
643         appData.zippyTalk = FALSE;
644         appData.noChessProgram = TRUE;
645 #endif
646         if (*appData.icsHelper != NULLCHAR) {
647             appData.useTelnet = TRUE;
648             appData.telnetProgram = appData.icsHelper;
649         }
650     } else {
651         appData.zippyTalk = appData.zippyPlay = FALSE;
652     }
653
654     /* [AS] Initialize pv info list [HGM] and game state */
655     {
656         int i, j;
657
658         for( i=0; i<=framePtr; i++ ) {
659             pvInfoList[i].depth = -1;
660             boards[i][EP_STATUS] = EP_NONE;
661             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
662         }
663     }
664
665     /*
666      * Parse timeControl resource
667      */
668     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669                           appData.movesPerSession)) {
670         char buf[MSG_SIZ];
671         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672         DisplayFatalError(buf, 0, 2);
673     }
674
675     /*
676      * Parse searchTime resource
677      */
678     if (*appData.searchTime != NULLCHAR) {
679         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
680         if (matched == 1) {
681             searchTime = min * 60;
682         } else if (matched == 2) {
683             searchTime = min * 60 + sec;
684         } else {
685             char buf[MSG_SIZ];
686             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687             DisplayFatalError(buf, 0, 2);
688         }
689     }
690
691     /* [AS] Adjudication threshold */
692     adjudicateLossThreshold = appData.adjudicateLossThreshold;
693     
694     first.which = "first";
695     second.which = "second";
696     first.maybeThinking = second.maybeThinking = FALSE;
697     first.pr = second.pr = NoProc;
698     first.isr = second.isr = NULL;
699     first.sendTime = second.sendTime = 2;
700     first.sendDrawOffers = 1;
701     if (appData.firstPlaysBlack) {
702         first.twoMachinesColor = "black\n";
703         second.twoMachinesColor = "white\n";
704     } else {
705         first.twoMachinesColor = "white\n";
706         second.twoMachinesColor = "black\n";
707     }
708     first.program = appData.firstChessProgram;
709     second.program = appData.secondChessProgram;
710     first.host = appData.firstHost;
711     second.host = appData.secondHost;
712     first.dir = appData.firstDirectory;
713     second.dir = appData.secondDirectory;
714     first.other = &second;
715     second.other = &first;
716     first.initString = appData.initString;
717     second.initString = appData.secondInitString;
718     first.computerString = appData.firstComputerString;
719     second.computerString = appData.secondComputerString;
720     first.useSigint = second.useSigint = TRUE;
721     first.useSigterm = second.useSigterm = TRUE;
722     first.reuse = appData.reuseFirst;
723     second.reuse = appData.reuseSecond;
724     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
725     second.nps = appData.secondNPS;
726     first.useSetboard = second.useSetboard = FALSE;
727     first.useSAN = second.useSAN = FALSE;
728     first.usePing = second.usePing = FALSE;
729     first.lastPing = second.lastPing = 0;
730     first.lastPong = second.lastPong = 0;
731     first.usePlayother = second.usePlayother = FALSE;
732     first.useColors = second.useColors = TRUE;
733     first.useUsermove = second.useUsermove = FALSE;
734     first.sendICS = second.sendICS = FALSE;
735     first.sendName = second.sendName = appData.icsActive;
736     first.sdKludge = second.sdKludge = FALSE;
737     first.stKludge = second.stKludge = FALSE;
738     TidyProgramName(first.program, first.host, first.tidy);
739     TidyProgramName(second.program, second.host, second.tidy);
740     first.matchWins = second.matchWins = 0;
741     strcpy(first.variants, appData.variant);
742     strcpy(second.variants, appData.variant);
743     first.analysisSupport = second.analysisSupport = 2; /* detect */
744     first.analyzing = second.analyzing = FALSE;
745     first.initDone = second.initDone = FALSE;
746
747     /* New features added by Tord: */
748     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750     /* End of new features added by Tord. */
751     first.fenOverride  = appData.fenOverride1;
752     second.fenOverride = appData.fenOverride2;
753
754     /* [HGM] time odds: set factor for each machine */
755     first.timeOdds  = appData.firstTimeOdds;
756     second.timeOdds = appData.secondTimeOdds;
757     { int norm = 1;
758         if(appData.timeOddsMode) {
759             norm = first.timeOdds;
760             if(norm > second.timeOdds) norm = second.timeOdds;
761         }
762         first.timeOdds /= norm;
763         second.timeOdds /= norm;
764     }
765
766     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767     first.accumulateTC = appData.firstAccumulateTC;
768     second.accumulateTC = appData.secondAccumulateTC;
769     first.maxNrOfSessions = second.maxNrOfSessions = 1;
770
771     /* [HGM] debug */
772     first.debug = second.debug = FALSE;
773     first.supportsNPS = second.supportsNPS = UNKNOWN;
774
775     /* [HGM] options */
776     first.optionSettings  = appData.firstOptions;
777     second.optionSettings = appData.secondOptions;
778
779     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781     first.isUCI = appData.firstIsUCI; /* [AS] */
782     second.isUCI = appData.secondIsUCI; /* [AS] */
783     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
785
786     if (appData.firstProtocolVersion > PROTOVER ||
787         appData.firstProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.firstProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       first.protocolVersion = appData.firstProtocolVersion;
794     }
795
796     if (appData.secondProtocolVersion > PROTOVER ||
797         appData.secondProtocolVersion < 1) {
798       char buf[MSG_SIZ];
799       sprintf(buf, _("protocol version %d not supported"),
800               appData.secondProtocolVersion);
801       DisplayFatalError(buf, 0, 2);
802     } else {
803       second.protocolVersion = appData.secondProtocolVersion;
804     }
805
806     if (appData.icsActive) {
807         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
808 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810         appData.clockMode = FALSE;
811         first.sendTime = second.sendTime = 0;
812     }
813     
814 #if ZIPPY
815     /* Override some settings from environment variables, for backward
816        compatibility.  Unfortunately it's not feasible to have the env
817        vars just set defaults, at least in xboard.  Ugh.
818     */
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
820       ZippyInit();
821     }
822 #endif
823     
824     if (appData.noChessProgram) {
825         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826         sprintf(programVersion, "%s", PACKAGE_STRING);
827     } else {
828       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
831     }
832
833     if (!appData.icsActive) {
834       char buf[MSG_SIZ];
835       /* Check for variants that are supported only in ICS mode,
836          or not at all.  Some that are accepted here nevertheless
837          have bugs; see comments below.
838       */
839       VariantClass variant = StringToVariant(appData.variant);
840       switch (variant) {
841       case VariantBughouse:     /* need four players and two boards */
842       case VariantKriegspiel:   /* need to hide pieces and move details */
843       /* case VariantFischeRandom: (Fabien: moved below) */
844         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845         DisplayFatalError(buf, 0, 2);
846         return;
847
848       case VariantUnknown:
849       case VariantLoadable:
850       case Variant29:
851       case Variant30:
852       case Variant31:
853       case Variant32:
854       case Variant33:
855       case Variant34:
856       case Variant35:
857       case Variant36:
858       default:
859         sprintf(buf, _("Unknown variant name %s"), appData.variant);
860         DisplayFatalError(buf, 0, 2);
861         return;
862
863       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
864       case VariantFairy:      /* [HGM] TestLegality definitely off! */
865       case VariantGothic:     /* [HGM] should work */
866       case VariantCapablanca: /* [HGM] should work */
867       case VariantCourier:    /* [HGM] initial forced moves not implemented */
868       case VariantShogi:      /* [HGM] drops not tested for legality */
869       case VariantKnightmate: /* [HGM] should work */
870       case VariantCylinder:   /* [HGM] untested */
871       case VariantFalcon:     /* [HGM] untested */
872       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873                                  offboard interposition not understood */
874       case VariantNormal:     /* definitely works! */
875       case VariantWildCastle: /* pieces not automatically shuffled */
876       case VariantNoCastle:   /* pieces not automatically shuffled */
877       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878       case VariantLosers:     /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantSuicide:    /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantGiveaway:   /* should work except for win condition,
883                                  and doesn't know captures are mandatory */
884       case VariantTwoKings:   /* should work */
885       case VariantAtomic:     /* should work except for win condition */
886       case Variant3Check:     /* should work except for win condition */
887       case VariantShatranj:   /* should work except for all win conditions */
888       case VariantBerolina:   /* might work if TestLegality is off */
889       case VariantCapaRandom: /* should work */
890       case VariantJanus:      /* should work */
891       case VariantSuper:      /* experimental */
892       case VariantGreat:      /* experimental, requires legality testing to be off */
893         break;
894       }
895     }
896
897     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
898     InitEngineUCI( installDir, &second );
899 }
900
901 int NextIntegerFromString( char ** str, long * value )
902 {
903     int result = -1;
904     char * s = *str;
905
906     while( *s == ' ' || *s == '\t' ) {
907         s++;
908     }
909
910     *value = 0;
911
912     if( *s >= '0' && *s <= '9' ) {
913         while( *s >= '0' && *s <= '9' ) {
914             *value = *value * 10 + (*s - '0');
915             s++;
916         }
917
918         result = 0;
919     }
920
921     *str = s;
922
923     return result;
924 }
925
926 int NextTimeControlFromString( char ** str, long * value )
927 {
928     long temp;
929     int result = NextIntegerFromString( str, &temp );
930
931     if( result == 0 ) {
932         *value = temp * 60; /* Minutes */
933         if( **str == ':' ) {
934             (*str)++;
935             result = NextIntegerFromString( str, &temp );
936             *value += temp; /* Seconds */
937         }
938     }
939
940     return result;
941 }
942
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
945     int result = -1; long temp, temp2;
946
947     if(**str != '+') return -1; // old params remain in force!
948     (*str)++;
949     if( NextTimeControlFromString( str, &temp ) ) return -1;
950
951     if(**str != '/') {
952         /* time only: incremental or sudden-death time control */
953         if(**str == '+') { /* increment follows; read it */
954             (*str)++;
955             if(result = NextIntegerFromString( str, &temp2)) return -1;
956             *inc = temp2 * 1000;
957         } else *inc = 0;
958         *moves = 0; *tc = temp * 1000; 
959         return 0;
960     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
961
962     (*str)++; /* classical time control */
963     result = NextTimeControlFromString( str, &temp2);
964     if(result == 0) {
965         *moves = temp/60;
966         *tc    = temp2 * 1000;
967         *inc   = 0;
968     }
969     return result;
970 }
971
972 int GetTimeQuota(int movenr)
973 {   /* [HGM] get time to add from the multi-session time-control string */
974     int moves=1; /* kludge to force reading of first session */
975     long time, increment;
976     char *s = fullTimeControlString;
977
978     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
979     do {
980         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982         if(movenr == -1) return time;    /* last move before new session     */
983         if(!moves) return increment;     /* current session is incremental   */
984         if(movenr >= 0) movenr -= moves; /* we already finished this session */
985     } while(movenr >= -1);               /* try again for next session       */
986
987     return 0; // no new time quota on this move
988 }
989
990 int
991 ParseTimeControl(tc, ti, mps)
992      char *tc;
993      int ti;
994      int mps;
995 {
996   long tc1;
997   long tc2;
998   char buf[MSG_SIZ];
999   
1000   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1001   if(ti > 0) {
1002     if(mps)
1003       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004     else sprintf(buf, "+%s+%d", tc, ti);
1005   } else {
1006     if(mps)
1007              sprintf(buf, "+%d/%s", mps, tc);
1008     else sprintf(buf, "+%s", tc);
1009   }
1010   fullTimeControlString = StrSave(buf);
1011   
1012   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1013     return FALSE;
1014   }
1015   
1016   if( *tc == '/' ) {
1017     /* Parse second time control */
1018     tc++;
1019     
1020     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1021       return FALSE;
1022     }
1023     
1024     if( tc2 == 0 ) {
1025       return FALSE;
1026     }
1027     
1028     timeControl_2 = tc2 * 1000;
1029   }
1030   else {
1031     timeControl_2 = 0;
1032   }
1033   
1034   if( tc1 == 0 ) {
1035     return FALSE;
1036   }
1037   
1038   timeControl = tc1 * 1000;
1039   
1040   if (ti >= 0) {
1041     timeIncrement = ti * 1000;  /* convert to ms */
1042     movesPerSession = 0;
1043   } else {
1044     timeIncrement = 0;
1045     movesPerSession = mps;
1046   }
1047   return TRUE;
1048 }
1049
1050 void
1051 InitBackEnd2()
1052 {
1053     if (appData.debugMode) {
1054         fprintf(debugFP, "%s\n", programVersion);
1055     }
1056
1057     set_cont_sequence(appData.wrapContSeq);
1058     if (appData.matchGames > 0) {
1059         appData.matchMode = TRUE;
1060     } else if (appData.matchMode) {
1061         appData.matchGames = 1;
1062     }
1063     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064         appData.matchGames = appData.sameColorGames;
1065     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1068     }
1069     Reset(TRUE, FALSE);
1070     if (appData.noChessProgram || first.protocolVersion == 1) {
1071       InitBackEnd3();
1072     } else {
1073       /* kludge: allow timeout for initial "feature" commands */
1074       FreezeUI();
1075       DisplayMessage("", _("Starting chess program"));
1076       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1077     }
1078 }
1079
1080 void
1081 InitBackEnd3 P((void))
1082 {
1083     GameMode initialMode;
1084     char buf[MSG_SIZ];
1085     int err;
1086
1087     InitChessProgram(&first, startedFromSetupPosition);
1088
1089
1090     if (appData.icsActive) {
1091 #ifdef WIN32
1092         /* [DM] Make a console window if needed [HGM] merged ifs */
1093         ConsoleCreate(); 
1094 #endif
1095         err = establish();
1096         if (err != 0) {
1097             if (*appData.icsCommPort != NULLCHAR) {
1098                 sprintf(buf, _("Could not open comm port %s"),  
1099                         appData.icsCommPort);
1100             } else {
1101                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1102                         appData.icsHost, appData.icsPort);
1103             }
1104             DisplayFatalError(buf, err, 1);
1105             return;
1106         }
1107         SetICSMode();
1108         telnetISR =
1109           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1110         fromUserISR =
1111           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112     } else if (appData.noChessProgram) {
1113         SetNCPMode();
1114     } else {
1115         SetGNUMode();
1116     }
1117
1118     if (*appData.cmailGameName != NULLCHAR) {
1119         SetCmailMode();
1120         OpenLoopback(&cmailPR);
1121         cmailISR =
1122           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1123     }
1124     
1125     ThawUI();
1126     DisplayMessage("", "");
1127     if (StrCaseCmp(appData.initialMode, "") == 0) {
1128       initialMode = BeginningOfGame;
1129     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130       initialMode = TwoMachinesPlay;
1131     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132       initialMode = AnalyzeFile; 
1133     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134       initialMode = AnalyzeMode;
1135     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136       initialMode = MachinePlaysWhite;
1137     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138       initialMode = MachinePlaysBlack;
1139     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140       initialMode = EditGame;
1141     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142       initialMode = EditPosition;
1143     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144       initialMode = Training;
1145     } else {
1146       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147       DisplayFatalError(buf, 0, 2);
1148       return;
1149     }
1150
1151     if (appData.matchMode) {
1152         /* Set up machine vs. machine match */
1153         if (appData.noChessProgram) {
1154             DisplayFatalError(_("Can't have a match with no chess programs"),
1155                               0, 2);
1156             return;
1157         }
1158         matchMode = TRUE;
1159         matchGame = 1;
1160         if (*appData.loadGameFile != NULLCHAR) {
1161             int index = appData.loadGameIndex; // [HGM] autoinc
1162             if(index<0) lastIndex = index = 1;
1163             if (!LoadGameFromFile(appData.loadGameFile,
1164                                   index,
1165                                   appData.loadGameFile, FALSE)) {
1166                 DisplayFatalError(_("Bad game file"), 0, 1);
1167                 return;
1168             }
1169         } else if (*appData.loadPositionFile != NULLCHAR) {
1170             int index = appData.loadPositionIndex; // [HGM] autoinc
1171             if(index<0) lastIndex = index = 1;
1172             if (!LoadPositionFromFile(appData.loadPositionFile,
1173                                       index,
1174                                       appData.loadPositionFile)) {
1175                 DisplayFatalError(_("Bad position file"), 0, 1);
1176                 return;
1177             }
1178         }
1179         TwoMachinesEvent();
1180     } else if (*appData.cmailGameName != NULLCHAR) {
1181         /* Set up cmail mode */
1182         ReloadCmailMsgEvent(TRUE);
1183     } else {
1184         /* Set up other modes */
1185         if (initialMode == AnalyzeFile) {
1186           if (*appData.loadGameFile == NULLCHAR) {
1187             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1188             return;
1189           }
1190         }
1191         if (*appData.loadGameFile != NULLCHAR) {
1192             (void) LoadGameFromFile(appData.loadGameFile,
1193                                     appData.loadGameIndex,
1194                                     appData.loadGameFile, TRUE);
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             (void) LoadPositionFromFile(appData.loadPositionFile,
1197                                         appData.loadPositionIndex,
1198                                         appData.loadPositionFile);
1199             /* [HGM] try to make self-starting even after FEN load */
1200             /* to allow automatic setup of fairy variants with wtm */
1201             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202                 gameMode = BeginningOfGame;
1203                 setboardSpoiledMachineBlack = 1;
1204             }
1205             /* [HGM] loadPos: make that every new game uses the setup */
1206             /* from file as long as we do not switch variant          */
1207             if(!blackPlaysFirst) {
1208                 startedFromPositionFile = TRUE;
1209                 CopyBoard(filePosition, boards[0]);
1210             }
1211         }
1212         if (initialMode == AnalyzeMode) {
1213           if (appData.noChessProgram) {
1214             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1215             return;
1216           }
1217           if (appData.icsActive) {
1218             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1219             return;
1220           }
1221           AnalyzeModeEvent();
1222         } else if (initialMode == AnalyzeFile) {
1223           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224           ShowThinkingEvent();
1225           AnalyzeFileEvent();
1226           AnalysisPeriodicEvent(1);
1227         } else if (initialMode == MachinePlaysWhite) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineWhiteEvent();
1239         } else if (initialMode == MachinePlaysBlack) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           MachineBlackEvent();
1251         } else if (initialMode == TwoMachinesPlay) {
1252           if (appData.noChessProgram) {
1253             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1254                               0, 2);
1255             return;
1256           }
1257           if (appData.icsActive) {
1258             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1259                               0, 2);
1260             return;
1261           }
1262           TwoMachinesEvent();
1263         } else if (initialMode == EditGame) {
1264           EditGameEvent();
1265         } else if (initialMode == EditPosition) {
1266           EditPositionEvent();
1267         } else if (initialMode == Training) {
1268           if (*appData.loadGameFile == NULLCHAR) {
1269             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1270             return;
1271           }
1272           TrainingEvent();
1273         }
1274     }
1275 }
1276
1277 /*
1278  * Establish will establish a contact to a remote host.port.
1279  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280  *  used to talk to the host.
1281  * Returns 0 if okay, error code if not.
1282  */
1283 int
1284 establish()
1285 {
1286     char buf[MSG_SIZ];
1287
1288     if (*appData.icsCommPort != NULLCHAR) {
1289         /* Talk to the host through a serial comm port */
1290         return OpenCommPort(appData.icsCommPort, &icsPR);
1291
1292     } else if (*appData.gateway != NULLCHAR) {
1293         if (*appData.remoteShell == NULLCHAR) {
1294             /* Use the rcmd protocol to run telnet program on a gateway host */
1295             snprintf(buf, sizeof(buf), "%s %s %s",
1296                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1297             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1298
1299         } else {
1300             /* Use the rsh program to run telnet program on a gateway host */
1301             if (*appData.remoteUser == NULLCHAR) {
1302                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303                         appData.gateway, appData.telnetProgram,
1304                         appData.icsHost, appData.icsPort);
1305             } else {
1306                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307                         appData.remoteShell, appData.gateway, 
1308                         appData.remoteUser, appData.telnetProgram,
1309                         appData.icsHost, appData.icsPort);
1310             }
1311             return StartChildProcess(buf, "", &icsPR);
1312
1313         }
1314     } else if (appData.useTelnet) {
1315         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1316
1317     } else {
1318         /* TCP socket interface differs somewhat between
1319            Unix and NT; handle details in the front end.
1320            */
1321         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1322     }
1323 }
1324
1325 void
1326 show_bytes(fp, buf, count)
1327      FILE *fp;
1328      char *buf;
1329      int count;
1330 {
1331     while (count--) {
1332         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333             fprintf(fp, "\\%03o", *buf & 0xff);
1334         } else {
1335             putc(*buf, fp);
1336         }
1337         buf++;
1338     }
1339     fflush(fp);
1340 }
1341
1342 /* Returns an errno value */
1343 int
1344 OutputMaybeTelnet(pr, message, count, outError)
1345      ProcRef pr;
1346      char *message;
1347      int count;
1348      int *outError;
1349 {
1350     char buf[8192], *p, *q, *buflim;
1351     int left, newcount, outcount;
1352
1353     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354         *appData.gateway != NULLCHAR) {
1355         if (appData.debugMode) {
1356             fprintf(debugFP, ">ICS: ");
1357             show_bytes(debugFP, message, count);
1358             fprintf(debugFP, "\n");
1359         }
1360         return OutputToProcess(pr, message, count, outError);
1361     }
1362
1363     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1364     p = message;
1365     q = buf;
1366     left = count;
1367     newcount = 0;
1368     while (left) {
1369         if (q >= buflim) {
1370             if (appData.debugMode) {
1371                 fprintf(debugFP, ">ICS: ");
1372                 show_bytes(debugFP, buf, newcount);
1373                 fprintf(debugFP, "\n");
1374             }
1375             outcount = OutputToProcess(pr, buf, newcount, outError);
1376             if (outcount < newcount) return -1; /* to be sure */
1377             q = buf;
1378             newcount = 0;
1379         }
1380         if (*p == '\n') {
1381             *q++ = '\r';
1382             newcount++;
1383         } else if (((unsigned char) *p) == TN_IAC) {
1384             *q++ = (char) TN_IAC;
1385             newcount ++;
1386         }
1387         *q++ = *p++;
1388         newcount++;
1389         left--;
1390     }
1391     if (appData.debugMode) {
1392         fprintf(debugFP, ">ICS: ");
1393         show_bytes(debugFP, buf, newcount);
1394         fprintf(debugFP, "\n");
1395     }
1396     outcount = OutputToProcess(pr, buf, newcount, outError);
1397     if (outcount < newcount) return -1; /* to be sure */
1398     return count;
1399 }
1400
1401 void
1402 read_from_player(isr, closure, message, count, error)
1403      InputSourceRef isr;
1404      VOIDSTAR closure;
1405      char *message;
1406      int count;
1407      int error;
1408 {
1409     int outError, outCount;
1410     static int gotEof = 0;
1411
1412     /* Pass data read from player on to ICS */
1413     if (count > 0) {
1414         gotEof = 0;
1415         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416         if (outCount < count) {
1417             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1418         }
1419     } else if (count < 0) {
1420         RemoveInputSource(isr);
1421         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422     } else if (gotEof++ > 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1425     }
1426 }
1427
1428 void
1429 KeepAlive()
1430 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431     SendToICS("date\n");
1432     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1433 }
1434
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1437 {
1438     char buffer[MSG_SIZ];
1439     va_list args;
1440
1441     va_start(args, format);
1442     vsnprintf(buffer, sizeof(buffer), format, args);
1443     buffer[sizeof(buffer)-1] = '\0';
1444     SendToICS(buffer);
1445     va_end(args);
1446 }
1447
1448 void
1449 SendToICS(s)
1450      char *s;
1451 {
1452     int count, outCount, outError;
1453
1454     if (icsPR == NULL) return;
1455
1456     count = strlen(s);
1457     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458     if (outCount < count) {
1459         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460     }
1461 }
1462
1463 /* This is used for sending logon scripts to the ICS. Sending
1464    without a delay causes problems when using timestamp on ICC
1465    (at least on my machine). */
1466 void
1467 SendToICSDelayed(s,msdelay)
1468      char *s;
1469      long msdelay;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     if (appData.debugMode) {
1477         fprintf(debugFP, ">ICS: ");
1478         show_bytes(debugFP, s, count);
1479         fprintf(debugFP, "\n");
1480     }
1481     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1482                                       msdelay);
1483     if (outCount < count) {
1484         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1485     }
1486 }
1487
1488
1489 /* Remove all highlighting escape sequences in s
1490    Also deletes any suffix starting with '(' 
1491    */
1492 char *
1493 StripHighlightAndTitle(s)
1494      char *s;
1495 {
1496     static char retbuf[MSG_SIZ];
1497     char *p = retbuf;
1498
1499     while (*s != NULLCHAR) {
1500         while (*s == '\033') {
1501             while (*s != NULLCHAR && !isalpha(*s)) s++;
1502             if (*s != NULLCHAR) s++;
1503         }
1504         while (*s != NULLCHAR && *s != '\033') {
1505             if (*s == '(' || *s == '[') {
1506                 *p = NULLCHAR;
1507                 return retbuf;
1508             }
1509             *p++ = *s++;
1510         }
1511     }
1512     *p = NULLCHAR;
1513     return retbuf;
1514 }
1515
1516 /* Remove all highlighting escape sequences in s */
1517 char *
1518 StripHighlight(s)
1519      char *s;
1520 {
1521     static char retbuf[MSG_SIZ];
1522     char *p = retbuf;
1523
1524     while (*s != NULLCHAR) {
1525         while (*s == '\033') {
1526             while (*s != NULLCHAR && !isalpha(*s)) s++;
1527             if (*s != NULLCHAR) s++;
1528         }
1529         while (*s != NULLCHAR && *s != '\033') {
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 char *variantNames[] = VARIANT_NAMES;
1538 char *
1539 VariantName(v)
1540      VariantClass v;
1541 {
1542     return variantNames[v];
1543 }
1544
1545
1546 /* Identify a variant from the strings the chess servers use or the
1547    PGN Variant tag names we use. */
1548 VariantClass
1549 StringToVariant(e)
1550      char *e;
1551 {
1552     char *p;
1553     int wnum = -1;
1554     VariantClass v = VariantNormal;
1555     int i, found = FALSE;
1556     char buf[MSG_SIZ];
1557
1558     if (!e) return v;
1559
1560     /* [HGM] skip over optional board-size prefixes */
1561     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563         while( *e++ != '_');
1564     }
1565
1566     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1567         v = VariantNormal;
1568         found = TRUE;
1569     } else
1570     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571       if (StrCaseStr(e, variantNames[i])) {
1572         v = (VariantClass) i;
1573         found = TRUE;
1574         break;
1575       }
1576     }
1577
1578     if (!found) {
1579       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580           || StrCaseStr(e, "wild/fr") 
1581           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582         v = VariantFischeRandom;
1583       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584                  (i = 1, p = StrCaseStr(e, "w"))) {
1585         p += i;
1586         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1587         if (isdigit(*p)) {
1588           wnum = atoi(p);
1589         } else {
1590           wnum = -1;
1591         }
1592         switch (wnum) {
1593         case 0: /* FICS only, actually */
1594         case 1:
1595           /* Castling legal even if K starts on d-file */
1596           v = VariantWildCastle;
1597           break;
1598         case 2:
1599         case 3:
1600         case 4:
1601           /* Castling illegal even if K & R happen to start in
1602              normal positions. */
1603           v = VariantNoCastle;
1604           break;
1605         case 5:
1606         case 7:
1607         case 8:
1608         case 10:
1609         case 11:
1610         case 12:
1611         case 13:
1612         case 14:
1613         case 15:
1614         case 18:
1615         case 19:
1616           /* Castling legal iff K & R start in normal positions */
1617           v = VariantNormal;
1618           break;
1619         case 6:
1620         case 20:
1621         case 21:
1622           /* Special wilds for position setup; unclear what to do here */
1623           v = VariantLoadable;
1624           break;
1625         case 9:
1626           /* Bizarre ICC game */
1627           v = VariantTwoKings;
1628           break;
1629         case 16:
1630           v = VariantKriegspiel;
1631           break;
1632         case 17:
1633           v = VariantLosers;
1634           break;
1635         case 22:
1636           v = VariantFischeRandom;
1637           break;
1638         case 23:
1639           v = VariantCrazyhouse;
1640           break;
1641         case 24:
1642           v = VariantBughouse;
1643           break;
1644         case 25:
1645           v = Variant3Check;
1646           break;
1647         case 26:
1648           /* Not quite the same as FICS suicide! */
1649           v = VariantGiveaway;
1650           break;
1651         case 27:
1652           v = VariantAtomic;
1653           break;
1654         case 28:
1655           v = VariantShatranj;
1656           break;
1657
1658         /* Temporary names for future ICC types.  The name *will* change in 
1659            the next xboard/WinBoard release after ICC defines it. */
1660         case 29:
1661           v = Variant29;
1662           break;
1663         case 30:
1664           v = Variant30;
1665           break;
1666         case 31:
1667           v = Variant31;
1668           break;
1669         case 32:
1670           v = Variant32;
1671           break;
1672         case 33:
1673           v = Variant33;
1674           break;
1675         case 34:
1676           v = Variant34;
1677           break;
1678         case 35:
1679           v = Variant35;
1680           break;
1681         case 36:
1682           v = Variant36;
1683           break;
1684         case 37:
1685           v = VariantShogi;
1686           break;
1687         case 38:
1688           v = VariantXiangqi;
1689           break;
1690         case 39:
1691           v = VariantCourier;
1692           break;
1693         case 40:
1694           v = VariantGothic;
1695           break;
1696         case 41:
1697           v = VariantCapablanca;
1698           break;
1699         case 42:
1700           v = VariantKnightmate;
1701           break;
1702         case 43:
1703           v = VariantFairy;
1704           break;
1705         case 44:
1706           v = VariantCylinder;
1707           break;
1708         case 45:
1709           v = VariantFalcon;
1710           break;
1711         case 46:
1712           v = VariantCapaRandom;
1713           break;
1714         case 47:
1715           v = VariantBerolina;
1716           break;
1717         case 48:
1718           v = VariantJanus;
1719           break;
1720         case 49:
1721           v = VariantSuper;
1722           break;
1723         case 50:
1724           v = VariantGreat;
1725           break;
1726         case -1:
1727           /* Found "wild" or "w" in the string but no number;
1728              must assume it's normal chess. */
1729           v = VariantNormal;
1730           break;
1731         default:
1732           sprintf(buf, _("Unknown wild type %d"), wnum);
1733           DisplayError(buf, 0);
1734           v = VariantUnknown;
1735           break;
1736         }
1737       }
1738     }
1739     if (appData.debugMode) {
1740       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741               e, wnum, VariantName(v));
1742     }
1743     return v;
1744 }
1745
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1748
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750    advance *index beyond it, and set leftover_start to the new value of
1751    *index; else return FALSE.  If pattern contains the character '*', it
1752    matches any sequence of characters not containing '\r', '\n', or the
1753    character following the '*' (if any), and the matched sequence(s) are
1754    copied into star_match.
1755    */
1756 int
1757 looking_at(buf, index, pattern)
1758      char *buf;
1759      int *index;
1760      char *pattern;
1761 {
1762     char *bufp = &buf[*index], *patternp = pattern;
1763     int star_count = 0;
1764     char *matchp = star_match[0];
1765     
1766     for (;;) {
1767         if (*patternp == NULLCHAR) {
1768             *index = leftover_start = bufp - buf;
1769             *matchp = NULLCHAR;
1770             return TRUE;
1771         }
1772         if (*bufp == NULLCHAR) return FALSE;
1773         if (*patternp == '*') {
1774             if (*bufp == *(patternp + 1)) {
1775                 *matchp = NULLCHAR;
1776                 matchp = star_match[++star_count];
1777                 patternp += 2;
1778                 bufp++;
1779                 continue;
1780             } else if (*bufp == '\n' || *bufp == '\r') {
1781                 patternp++;
1782                 if (*patternp == NULLCHAR)
1783                   continue;
1784                 else
1785                   return FALSE;
1786             } else {
1787                 *matchp++ = *bufp++;
1788                 continue;
1789             }
1790         }
1791         if (*patternp != *bufp) return FALSE;
1792         patternp++;
1793         bufp++;
1794     }
1795 }
1796
1797 void
1798 SendToPlayer(data, length)
1799      char *data;
1800      int length;
1801 {
1802     int error, outCount;
1803     outCount = OutputToProcess(NoProc, data, length, &error);
1804     if (outCount < length) {
1805         DisplayFatalError(_("Error writing to display"), error, 1);
1806     }
1807 }
1808
1809 void
1810 PackHolding(packed, holding)
1811      char packed[];
1812      char *holding;
1813 {
1814     char *p = holding;
1815     char *q = packed;
1816     int runlength = 0;
1817     int curr = 9999;
1818     do {
1819         if (*p == curr) {
1820             runlength++;
1821         } else {
1822             switch (runlength) {
1823               case 0:
1824                 break;
1825               case 1:
1826                 *q++ = curr;
1827                 break;
1828               case 2:
1829                 *q++ = curr;
1830                 *q++ = curr;
1831                 break;
1832               default:
1833                 sprintf(q, "%d", runlength);
1834                 while (*q) q++;
1835                 *q++ = curr;
1836                 break;
1837             }
1838             runlength = 1;
1839             curr = *p;
1840         }
1841     } while (*p++);
1842     *q = NULLCHAR;
1843 }
1844
1845 /* Telnet protocol requests from the front end */
1846 void
1847 TelnetRequest(ddww, option)
1848      unsigned char ddww, option;
1849 {
1850     unsigned char msg[3];
1851     int outCount, outError;
1852
1853     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1854
1855     if (appData.debugMode) {
1856         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1857         switch (ddww) {
1858           case TN_DO:
1859             ddwwStr = "DO";
1860             break;
1861           case TN_DONT:
1862             ddwwStr = "DONT";
1863             break;
1864           case TN_WILL:
1865             ddwwStr = "WILL";
1866             break;
1867           case TN_WONT:
1868             ddwwStr = "WONT";
1869             break;
1870           default:
1871             ddwwStr = buf1;
1872             sprintf(buf1, "%d", ddww);
1873             break;
1874         }
1875         switch (option) {
1876           case TN_ECHO:
1877             optionStr = "ECHO";
1878             break;
1879           default:
1880             optionStr = buf2;
1881             sprintf(buf2, "%d", option);
1882             break;
1883         }
1884         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1885     }
1886     msg[0] = TN_IAC;
1887     msg[1] = ddww;
1888     msg[2] = option;
1889     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1890     if (outCount < 3) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895 void
1896 DoEcho()
1897 {
1898     if (!appData.icsActive) return;
1899     TelnetRequest(TN_DO, TN_ECHO);
1900 }
1901
1902 void
1903 DontEcho()
1904 {
1905     if (!appData.icsActive) return;
1906     TelnetRequest(TN_DONT, TN_ECHO);
1907 }
1908
1909 void
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1911 {
1912     /* put the holdings sent to us by the server on the board holdings area */
1913     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1914     char p;
1915     ChessSquare piece;
1916
1917     if(gameInfo.holdingsWidth < 2)  return;
1918     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919         return; // prevent overwriting by pre-board holdings
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948 }
1949
1950
1951 void
1952 VariantSwitch(Board board, VariantClass newVariant)
1953 {
1954    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1955    Board oldBoard;
1956
1957    startedFromPositionFile = FALSE;
1958    if(gameInfo.variant == newVariant) return;
1959
1960    /* [HGM] This routine is called each time an assignment is made to
1961     * gameInfo.variant during a game, to make sure the board sizes
1962     * are set to match the new variant. If that means adding or deleting
1963     * holdings, we shift the playing board accordingly
1964     * This kludge is needed because in ICS observe mode, we get boards
1965     * of an ongoing game without knowing the variant, and learn about the
1966     * latter only later. This can be because of the move list we requested,
1967     * in which case the game history is refilled from the beginning anyway,
1968     * but also when receiving holdings of a crazyhouse game. In the latter
1969     * case we want to add those holdings to the already received position.
1970     */
1971
1972    
1973    if (appData.debugMode) {
1974      fprintf(debugFP, "Switch board from %s to %s\n",
1975              VariantName(gameInfo.variant), VariantName(newVariant));
1976      setbuf(debugFP, NULL);
1977    }
1978    shuffleOpenings = 0;       /* [HGM] shuffle */
1979    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1980    switch(newVariant) 
1981      {
1982      case VariantShogi:
1983        newWidth = 9;  newHeight = 9;
1984        gameInfo.holdingsSize = 7;
1985      case VariantBughouse:
1986      case VariantCrazyhouse:
1987        newHoldingsWidth = 2; break;
1988      case VariantGreat:
1989        newWidth = 10;
1990      case VariantSuper:
1991        newHoldingsWidth = 2;
1992        gameInfo.holdingsSize = 8;
1993        break;
1994      case VariantGothic:
1995      case VariantCapablanca:
1996      case VariantCapaRandom:
1997        newWidth = 10;
1998      default:
1999        newHoldingsWidth = gameInfo.holdingsSize = 0;
2000      };
2001    
2002    if(newWidth  != gameInfo.boardWidth  ||
2003       newHeight != gameInfo.boardHeight ||
2004       newHoldingsWidth != gameInfo.holdingsWidth ) {
2005      
2006      /* shift position to new playing area, if needed */
2007      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008        for(i=0; i<BOARD_HEIGHT; i++) 
2009          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011              board[i][j];
2012        for(i=0; i<newHeight; i++) {
2013          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015        }
2016      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017        for(i=0; i<BOARD_HEIGHT; i++)
2018          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2020              board[i][j];
2021      }
2022      gameInfo.boardWidth  = newWidth;
2023      gameInfo.boardHeight = newHeight;
2024      gameInfo.holdingsWidth = newHoldingsWidth;
2025      gameInfo.variant = newVariant;
2026      InitDrawingSizes(-2, 0);
2027    } else gameInfo.variant = newVariant;
2028    CopyBoard(oldBoard, board);   // remember correctly formatted board
2029      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2030    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2031 }
2032
2033 static int loggedOn = FALSE;
2034
2035 /*-- Game start info cache: --*/
2036 int gs_gamenum;
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\   ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2044
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2047
2048 void
2049 read_from_ics(isr, closure, data, count, error)
2050      InputSourceRef isr;
2051      VOIDSTAR closure;
2052      char *data;
2053      int count;
2054      int error;
2055 {
2056 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2065     
2066     static int started = STARTED_NONE;
2067     static char parse[20000];
2068     static int parse_pos = 0;
2069     static char buf[BUF_SIZE + 1];
2070     static int firstTime = TRUE, intfSet = FALSE;
2071     static ColorClass prevColor = ColorNormal;
2072     static int savingComment = FALSE;
2073     static int cmatch = 0; // continuation sequence match
2074     char *bp;
2075     char str[500];
2076     int i, oldi;
2077     int buf_len;
2078     int next_out;
2079     int tkind;
2080     int backup;    /* [DM] For zippy color lines */
2081     char *p;
2082     char talker[MSG_SIZ]; // [HGM] chat
2083     int channel;
2084
2085     if (appData.debugMode) {
2086       if (!error) {
2087         fprintf(debugFP, "<ICS: ");
2088         show_bytes(debugFP, data, count);
2089         fprintf(debugFP, "\n");
2090       }
2091     }
2092
2093     if (appData.debugMode) { int f = forwardMostMove;
2094         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106     /* copy new characters into the buffer */
2107     bp = buf + leftover_len;
2108     buf_len=leftover_len;
2109     for (i=0; i<count; i++)
2110     {
2111         // ignore these
2112         if (data[i] == '\r')
2113             continue;
2114
2115         // join lines split by ICS?
2116         if (!appData.noJoin)
2117         {
2118             /*
2119                 Joining just consists of finding matches against the
2120                 continuation sequence, and discarding that sequence
2121                 if found instead of copying it.  So, until a match
2122                 fails, there's nothing to do since it might be the
2123                 complete sequence, and thus, something we don't want
2124                 copied.
2125             */
2126             if (data[i] == cont_seq[cmatch])
2127             {
2128                 cmatch++;
2129                 if (cmatch == strlen(cont_seq))
2130                 {
2131                     cmatch = 0; // complete match.  just reset the counter
2132
2133                     /*
2134                         it's possible for the ICS to not include the space
2135                         at the end of the last word, making our [correct]
2136                         join operation fuse two separate words.  the server
2137                         does this when the space occurs at the width setting.
2138                     */
2139                     if (!buf_len || buf[buf_len-1] != ' ')
2140                     {
2141                         *bp++ = ' ';
2142                         buf_len++;
2143                     }
2144                 }
2145                 continue;
2146             }
2147             else if (cmatch)
2148             {
2149                 /*
2150                     match failed, so we have to copy what matched before
2151                     falling through and copying this character.  In reality,
2152                     this will only ever be just the newline character, but
2153                     it doesn't hurt to be precise.
2154                 */
2155                 strncpy(bp, cont_seq, cmatch);
2156                 bp += cmatch;
2157                 buf_len += cmatch;
2158                 cmatch = 0;
2159             }
2160         }
2161
2162         // copy this char
2163         *bp++ = data[i];
2164         buf_len++;
2165     }
2166
2167         buf[buf_len] = NULLCHAR;
2168         next_out = leftover_len;
2169         leftover_start = 0;
2170         
2171         i = 0;
2172         while (i < buf_len) {
2173             /* Deal with part of the TELNET option negotiation
2174                protocol.  We refuse to do anything beyond the
2175                defaults, except that we allow the WILL ECHO option,
2176                which ICS uses to turn off password echoing when we are
2177                directly connected to it.  We reject this option
2178                if localLineEditing mode is on (always on in xboard)
2179                and we are talking to port 23, which might be a real
2180                telnet server that will try to keep WILL ECHO on permanently.
2181              */
2182             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184                 unsigned char option;
2185                 oldi = i;
2186                 switch ((unsigned char) buf[++i]) {
2187                   case TN_WILL:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<WILL ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       case TN_ECHO:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "ECHO ");
2194                         /* Reply only if this is a change, according
2195                            to the protocol rules. */
2196                         if (remoteEchoOption) break;
2197                         if (appData.localLineEditing &&
2198                             atoi(appData.icsPort) == TN_PORT) {
2199                             TelnetRequest(TN_DONT, TN_ECHO);
2200                         } else {
2201                             EchoOff();
2202                             TelnetRequest(TN_DO, TN_ECHO);
2203                             remoteEchoOption = TRUE;
2204                         }
2205                         break;
2206                       default:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "%d ", option);
2209                         /* Whatever this is, we don't want it. */
2210                         TelnetRequest(TN_DONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_WONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<WONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       case TN_ECHO:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "ECHO ");
2221                         /* Reply only if this is a change, according
2222                            to the protocol rules. */
2223                         if (!remoteEchoOption) break;
2224                         EchoOn();
2225                         TelnetRequest(TN_DONT, TN_ECHO);
2226                         remoteEchoOption = FALSE;
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", (unsigned char) option);
2231                         /* Whatever this is, it must already be turned
2232                            off, because we never agree to turn on
2233                            anything non-default, so according to the
2234                            protocol rules, we don't reply. */
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DO:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DO ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         /* Whatever this is, we refuse to do it. */
2244                         if (appData.debugMode)
2245                           fprintf(debugFP, "%d ", option);
2246                         TelnetRequest(TN_WONT, option);
2247                         break;
2248                     }
2249                     break;
2250                   case TN_DONT:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<DONT ");
2253                     switch (option = (unsigned char) buf[++i]) {
2254                       default:
2255                         if (appData.debugMode)
2256                           fprintf(debugFP, "%d ", option);
2257                         /* Whatever this is, we are already not doing
2258                            it, because we never agree to do anything
2259                            non-default, so according to the protocol
2260                            rules, we don't reply. */
2261                         break;
2262                     }
2263                     break;
2264                   case TN_IAC:
2265                     if (appData.debugMode)
2266                       fprintf(debugFP, "\n<IAC ");
2267                     /* Doubled IAC; pass it through */
2268                     i--;
2269                     break;
2270                   default:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273                     /* Drop all other telnet commands on the floor */
2274                     break;
2275                 }
2276                 if (oldi > next_out)
2277                   SendToPlayer(&buf[next_out], oldi - next_out);
2278                 if (++i > next_out)
2279                   next_out = i;
2280                 continue;
2281             }
2282                 
2283             /* OK, this at least will *usually* work */
2284             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2285                 loggedOn = TRUE;
2286             }
2287             
2288             if (loggedOn && !intfSet) {
2289                 if (ics_type == ICS_ICC) {
2290                   sprintf(str,
2291                           "/set-quietly interface %s\n/set-quietly style 12\n",
2292                           programVersion);
2293                 } else if (ics_type == ICS_CHESSNET) {
2294                   sprintf(str, "/style 12\n");
2295                 } else {
2296                   strcpy(str, "alias $ @\n$set interface ");
2297                   strcat(str, programVersion);
2298                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 #ifdef WIN32
2300                   strcat(str, "$iset nohighlight 1\n");
2301 #endif
2302                   strcat(str, "$iset lock 1\n$style 12\n");
2303                 }
2304                 SendToICS(str);
2305                 NotifyFrontendLogin();
2306                 intfSet = TRUE;
2307             }
2308
2309             if (started == STARTED_COMMENT) {
2310                 /* Accumulate characters in comment */
2311                 parse[parse_pos++] = buf[i];
2312                 if (buf[i] == '\n') {
2313                     parse[parse_pos] = NULLCHAR;
2314                     if(chattingPartner>=0) {
2315                         char mess[MSG_SIZ];
2316                         sprintf(mess, "%s%s", talker, parse);
2317                         OutputChatMessage(chattingPartner, mess);
2318                         chattingPartner = -1;
2319                     } else
2320                     if(!suppressKibitz) // [HGM] kibitz
2321                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323                         int nrDigit = 0, nrAlph = 0, i;
2324                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326                         parse[parse_pos] = NULLCHAR;
2327                         // try to be smart: if it does not look like search info, it should go to
2328                         // ICS interaction window after all, not to engine-output window.
2329                         for(i=0; i<parse_pos; i++) { // count letters and digits
2330                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2332                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2333                         }
2334                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335                             int depth=0; float score;
2336                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338                                 pvInfoList[forwardMostMove-1].depth = depth;
2339                                 pvInfoList[forwardMostMove-1].score = 100*score;
2340                             }
2341                             OutputKibitz(suppressKibitz, parse);
2342                         } else {
2343                             char tmp[MSG_SIZ];
2344                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345                             SendToPlayer(tmp, strlen(tmp));
2346                         }
2347                     }
2348                     started = STARTED_NONE;
2349                 } else {
2350                     /* Don't match patterns against characters in chatter */
2351                     i++;
2352                     continue;
2353                 }
2354             }
2355             if (started == STARTED_CHATTER) {
2356                 if (buf[i] != '\n') {
2357                     /* Don't match patterns against characters in chatter */
2358                     i++;
2359                     continue;
2360                 }
2361                 started = STARTED_NONE;
2362             }
2363
2364             /* Kludge to deal with rcmd protocol */
2365             if (firstTime && looking_at(buf, &i, "\001*")) {
2366                 DisplayFatalError(&buf[1], 0, 1);
2367                 continue;
2368             } else {
2369                 firstTime = FALSE;
2370             }
2371
2372             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2373                 ics_type = ICS_ICC;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380                 ics_type = ICS_FICS;
2381                 ics_prefix = "$";
2382                 if (appData.debugMode)
2383                   fprintf(debugFP, "ics_type %d\n", ics_type);
2384                 continue;
2385             }
2386             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387                 ics_type = ICS_CHESSNET;
2388                 ics_prefix = "/";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393
2394             if (!loggedOn &&
2395                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2397                  looking_at(buf, &i, "will be \"*\""))) {
2398               strcpy(ics_handle, star_match[0]);
2399               continue;
2400             }
2401
2402             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403               char buf[MSG_SIZ];
2404               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405               DisplayIcsInteractionTitle(buf);
2406               have_set_title = TRUE;
2407             }
2408
2409             /* skip finger notes */
2410             if (started == STARTED_NONE &&
2411                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412                  (buf[i] == '1' && buf[i+1] == '0')) &&
2413                 buf[i+2] == ':' && buf[i+3] == ' ') {
2414               started = STARTED_CHATTER;
2415               i += 3;
2416               continue;
2417             }
2418
2419             /* skip formula vars */
2420             if (started == STARTED_NONE &&
2421                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422               started = STARTED_CHATTER;
2423               i += 3;
2424               continue;
2425             }
2426
2427             oldi = i;
2428             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429             if (appData.autoKibitz && started == STARTED_NONE && 
2430                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2431                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432                 if(looking_at(buf, &i, "* kibitzes: ") &&
2433                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2434                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2435                         suppressKibitz = TRUE;
2436                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437                                 && (gameMode == IcsPlayingWhite)) ||
2438                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2440                             started = STARTED_CHATTER; // own kibitz we simply discard
2441                         else {
2442                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443                             parse_pos = 0; parse[0] = NULLCHAR;
2444                             savingComment = TRUE;
2445                             suppressKibitz = gameMode != IcsObserving ? 2 :
2446                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2447                         } 
2448                         continue;
2449                 } else
2450                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451                     started = STARTED_CHATTER;
2452                     suppressKibitz = TRUE;
2453                 }
2454             } // [HGM] kibitz: end of patch
2455
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457
2458             // [HGM] chat: intercept tells by users for which we have an open chat window
2459             channel = -1;
2460             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2461                                            looking_at(buf, &i, "* whispers:") ||
2462                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464                 int p;
2465                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466                 chattingPartner = -1;
2467
2468                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469                 for(p=0; p<MAX_CHAT; p++) {
2470                     if(channel == atoi(chatPartner[p])) {
2471                     talker[0] = '['; strcat(talker, "]");
2472                     chattingPartner = p; break;
2473                     }
2474                 } else
2475                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476                 for(p=0; p<MAX_CHAT; p++) {
2477                     if(!strcmp("WHISPER", chatPartner[p])) {
2478                         talker[0] = '['; strcat(talker, "]");
2479                         chattingPartner = p; break;
2480                     }
2481                 }
2482                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484                     talker[0] = 0;
2485                     chattingPartner = p; break;
2486                 }
2487                 if(chattingPartner<0) i = oldi; else {
2488                     started = STARTED_COMMENT;
2489                     parse_pos = 0; parse[0] = NULLCHAR;
2490                     savingComment = TRUE;
2491                     suppressKibitz = TRUE;
2492                 }
2493             } // [HGM] chat: end of patch
2494
2495             if (appData.zippyTalk || appData.zippyPlay) {
2496                 /* [DM] Backup address for color zippy lines */
2497                 backup = i;
2498 #if ZIPPY
2499        #ifdef WIN32
2500                if (loggedOn == TRUE)
2501                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503        #else
2504                 if (ZippyControl(buf, &i) ||
2505                     ZippyConverse(buf, &i) ||
2506                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507                       loggedOn = TRUE;
2508                       if (!appData.colorize) continue;
2509                 }
2510        #endif
2511 #endif
2512             } // [DM] 'else { ' deleted
2513                 if (
2514                     /* Regular tells and says */
2515                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2517                     looking_at(buf, &i, "* says: ") ||
2518                     /* Don't color "message" or "messages" output */
2519                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520                     looking_at(buf, &i, "*. * at *:*: ") ||
2521                     looking_at(buf, &i, "--* (*:*): ") ||
2522                     /* Message notifications (same color as tells) */
2523                     looking_at(buf, &i, "* has left a message ") ||
2524                     looking_at(buf, &i, "* just sent you a message:\n") ||
2525                     /* Whispers and kibitzes */
2526                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527                     looking_at(buf, &i, "* kibitzes: ") ||
2528                     /* Channel tells */
2529                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530
2531                   if (tkind == 1 && strchr(star_match[0], ':')) {
2532                       /* Avoid "tells you:" spoofs in channels */
2533                      tkind = 3;
2534                   }
2535                   if (star_match[0][0] == NULLCHAR ||
2536                       strchr(star_match[0], ' ') ||
2537                       (tkind == 3 && strchr(star_match[1], ' '))) {
2538                     /* Reject bogus matches */
2539                     i = oldi;
2540                   } else {
2541                     if (appData.colorize) {
2542                       if (oldi > next_out) {
2543                         SendToPlayer(&buf[next_out], oldi - next_out);
2544                         next_out = oldi;
2545                       }
2546                       switch (tkind) {
2547                       case 1:
2548                         Colorize(ColorTell, FALSE);
2549                         curColor = ColorTell;
2550                         break;
2551                       case 2:
2552                         Colorize(ColorKibitz, FALSE);
2553                         curColor = ColorKibitz;
2554                         break;
2555                       case 3:
2556                         p = strrchr(star_match[1], '(');
2557                         if (p == NULL) {
2558                           p = star_match[1];
2559                         } else {
2560                           p++;
2561                         }
2562                         if (atoi(p) == 1) {
2563                           Colorize(ColorChannel1, FALSE);
2564                           curColor = ColorChannel1;
2565                         } else {
2566                           Colorize(ColorChannel, FALSE);
2567                           curColor = ColorChannel;
2568                         }
2569                         break;
2570                       case 5:
2571                         curColor = ColorNormal;
2572                         break;
2573                       }
2574                     }
2575                     if (started == STARTED_NONE && appData.autoComment &&
2576                         (gameMode == IcsObserving ||
2577                          gameMode == IcsPlayingWhite ||
2578                          gameMode == IcsPlayingBlack)) {
2579                       parse_pos = i - oldi;
2580                       memcpy(parse, &buf[oldi], parse_pos);
2581                       parse[parse_pos] = NULLCHAR;
2582                       started = STARTED_COMMENT;
2583                       savingComment = TRUE;
2584                     } else {
2585                       started = STARTED_CHATTER;
2586                       savingComment = FALSE;
2587                     }
2588                     loggedOn = TRUE;
2589                     continue;
2590                   }
2591                 }
2592
2593                 if (looking_at(buf, &i, "* s-shouts: ") ||
2594                     looking_at(buf, &i, "* c-shouts: ")) {
2595                     if (appData.colorize) {
2596                         if (oldi > next_out) {
2597                             SendToPlayer(&buf[next_out], oldi - next_out);
2598                             next_out = oldi;
2599                         }
2600                         Colorize(ColorSShout, FALSE);
2601                         curColor = ColorSShout;
2602                     }
2603                     loggedOn = TRUE;
2604                     started = STARTED_CHATTER;
2605                     continue;
2606                 }
2607
2608                 if (looking_at(buf, &i, "--->")) {
2609                     loggedOn = TRUE;
2610                     continue;
2611                 }
2612
2613                 if (looking_at(buf, &i, "* shouts: ") ||
2614                     looking_at(buf, &i, "--> ")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorShout, FALSE);
2621                         curColor = ColorShout;
2622                     }
2623                     loggedOn = TRUE;
2624                     started = STARTED_CHATTER;
2625                     continue;
2626                 }
2627
2628                 if (looking_at( buf, &i, "Challenge:")) {
2629                     if (appData.colorize) {
2630                         if (oldi > next_out) {
2631                             SendToPlayer(&buf[next_out], oldi - next_out);
2632                             next_out = oldi;
2633                         }
2634                         Colorize(ColorChallenge, FALSE);
2635                         curColor = ColorChallenge;
2636                     }
2637                     loggedOn = TRUE;
2638                     continue;
2639                 }
2640
2641                 if (looking_at(buf, &i, "* offers you") ||
2642                     looking_at(buf, &i, "* offers to be") ||
2643                     looking_at(buf, &i, "* would like to") ||
2644                     looking_at(buf, &i, "* requests to") ||
2645                     looking_at(buf, &i, "Your opponent offers") ||
2646                     looking_at(buf, &i, "Your opponent requests")) {
2647
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorRequest, FALSE);
2654                         curColor = ColorRequest;
2655                     }
2656                     continue;
2657                 }
2658
2659                 if (looking_at(buf, &i, "* (*) seeking")) {
2660                     if (appData.colorize) {
2661                         if (oldi > next_out) {
2662                             SendToPlayer(&buf[next_out], oldi - next_out);
2663                             next_out = oldi;
2664                         }
2665                         Colorize(ColorSeek, FALSE);
2666                         curColor = ColorSeek;
2667                     }
2668                     continue;
2669             }
2670
2671             if (looking_at(buf, &i, "\\   ")) {
2672                 if (prevColor != ColorNormal) {
2673                     if (oldi > next_out) {
2674                         SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = oldi;
2676                     }
2677                     Colorize(prevColor, TRUE);
2678                     curColor = prevColor;
2679                 }
2680                 if (savingComment) {
2681                     parse_pos = i - oldi;
2682                     memcpy(parse, &buf[oldi], parse_pos);
2683                     parse[parse_pos] = NULLCHAR;
2684                     started = STARTED_COMMENT;
2685                 } else {
2686                     started = STARTED_CHATTER;
2687                 }
2688                 continue;
2689             }
2690
2691             if (looking_at(buf, &i, "Black Strength :") ||
2692                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693                 looking_at(buf, &i, "<10>") ||
2694                 looking_at(buf, &i, "#@#")) {
2695                 /* Wrong board style */
2696                 loggedOn = TRUE;
2697                 SendToICS(ics_prefix);
2698                 SendToICS("set style 12\n");
2699                 SendToICS(ics_prefix);
2700                 SendToICS("refresh\n");
2701                 continue;
2702             }
2703             
2704             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705                 ICSInitScript();
2706                 have_sent_ICS_logon = 1;
2707                 continue;
2708             }
2709               
2710             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2711                 (looking_at(buf, &i, "\n<12> ") ||
2712                  looking_at(buf, &i, "<12> "))) {
2713                 loggedOn = TRUE;
2714                 if (oldi > next_out) {
2715                     SendToPlayer(&buf[next_out], oldi - next_out);
2716                 }
2717                 next_out = i;
2718                 started = STARTED_BOARD;
2719                 parse_pos = 0;
2720                 continue;
2721             }
2722
2723             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724                 looking_at(buf, &i, "<b1> ")) {
2725                 if (oldi > next_out) {
2726                     SendToPlayer(&buf[next_out], oldi - next_out);
2727                 }
2728                 next_out = i;
2729                 started = STARTED_HOLDINGS;
2730                 parse_pos = 0;
2731                 continue;
2732             }
2733
2734             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735                 loggedOn = TRUE;
2736                 /* Header for a move list -- first line */
2737
2738                 switch (ics_getting_history) {
2739                   case H_FALSE:
2740                     switch (gameMode) {
2741                       case IcsIdle:
2742                       case BeginningOfGame:
2743                         /* User typed "moves" or "oldmoves" while we
2744                            were idle.  Pretend we asked for these
2745                            moves and soak them up so user can step
2746                            through them and/or save them.
2747                            */
2748                         Reset(FALSE, TRUE);
2749                         gameMode = IcsObserving;
2750                         ModeHighlight();
2751                         ics_gamenum = -1;
2752                         ics_getting_history = H_GOT_UNREQ_HEADER;
2753                         break;
2754                       case EditGame: /*?*/
2755                       case EditPosition: /*?*/
2756                         /* Should above feature work in these modes too? */
2757                         /* For now it doesn't */
2758                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2759                         break;
2760                       default:
2761                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2762                         break;
2763                     }
2764                     break;
2765                   case H_REQUESTED:
2766                     /* Is this the right one? */
2767                     if (gameInfo.white && gameInfo.black &&
2768                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2769                         strcmp(gameInfo.black, star_match[2]) == 0) {
2770                         /* All is well */
2771                         ics_getting_history = H_GOT_REQ_HEADER;
2772                     }
2773                     break;
2774                   case H_GOT_REQ_HEADER:
2775                   case H_GOT_UNREQ_HEADER:
2776                   case H_GOT_UNWANTED_HEADER:
2777                   case H_GETTING_MOVES:
2778                     /* Should not happen */
2779                     DisplayError(_("Error gathering move list: two headers"), 0);
2780                     ics_getting_history = H_FALSE;
2781                     break;
2782                 }
2783
2784                 /* Save player ratings into gameInfo if needed */
2785                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787                     (gameInfo.whiteRating == -1 ||
2788                      gameInfo.blackRating == -1)) {
2789
2790                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2791                     gameInfo.blackRating = string_to_rating(star_match[3]);
2792                     if (appData.debugMode)
2793                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2794                               gameInfo.whiteRating, gameInfo.blackRating);
2795                 }
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i,
2800               "* * match, initial time: * minute*, increment: * second")) {
2801                 /* Header for a move list -- second line */
2802                 /* Initial board will follow if this is a wild game */
2803                 if (gameInfo.event != NULL) free(gameInfo.event);
2804                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805                 gameInfo.event = StrSave(str);
2806                 /* [HGM] we switched variant. Translate boards if needed. */
2807                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2808                 continue;
2809             }
2810
2811             if (looking_at(buf, &i, "Move  ")) {
2812                 /* Beginning of a move list */
2813                 switch (ics_getting_history) {
2814                   case H_FALSE:
2815                     /* Normally should not happen */
2816                     /* Maybe user hit reset while we were parsing */
2817                     break;
2818                   case H_REQUESTED:
2819                     /* Happens if we are ignoring a move list that is not
2820                      * the one we just requested.  Common if the user
2821                      * tries to observe two games without turning off
2822                      * getMoveList */
2823                     break;
2824                   case H_GETTING_MOVES:
2825                     /* Should not happen */
2826                     DisplayError(_("Error gathering move list: nested"), 0);
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                   case H_GOT_REQ_HEADER:
2830                     ics_getting_history = H_GETTING_MOVES;
2831                     started = STARTED_MOVES;
2832                     parse_pos = 0;
2833                     if (oldi > next_out) {
2834                         SendToPlayer(&buf[next_out], oldi - next_out);
2835                     }
2836                     break;
2837                   case H_GOT_UNREQ_HEADER:
2838                     ics_getting_history = H_GETTING_MOVES;
2839                     started = STARTED_MOVES_NOHIDE;
2840                     parse_pos = 0;
2841                     break;
2842                   case H_GOT_UNWANTED_HEADER:
2843                     ics_getting_history = H_FALSE;
2844                     break;
2845                 }
2846                 continue;
2847             }                           
2848             
2849             if (looking_at(buf, &i, "% ") ||
2850                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852                 savingComment = FALSE;
2853                 switch (started) {
2854                   case STARTED_MOVES:
2855                   case STARTED_MOVES_NOHIDE:
2856                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857                     parse[parse_pos + i - oldi] = NULLCHAR;
2858                     ParseGameHistory(parse);
2859 #if ZIPPY
2860                     if (appData.zippyPlay && first.initDone) {
2861                         FeedMovesToProgram(&first, forwardMostMove);
2862                         if (gameMode == IcsPlayingWhite) {
2863                             if (WhiteOnMove(forwardMostMove)) {
2864                                 if (first.sendTime) {
2865                                   if (first.useColors) {
2866                                     SendToProgram("black\n", &first); 
2867                                   }
2868                                   SendTimeRemaining(&first, TRUE);
2869                                 }
2870                                 if (first.useColors) {
2871                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872                                 }
2873                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874                                 first.maybeThinking = TRUE;
2875                             } else {
2876                                 if (first.usePlayother) {
2877                                   if (first.sendTime) {
2878                                     SendTimeRemaining(&first, TRUE);
2879                                   }
2880                                   SendToProgram("playother\n", &first);
2881                                   firstMove = FALSE;
2882                                 } else {
2883                                   firstMove = TRUE;
2884                                 }
2885                             }
2886                         } else if (gameMode == IcsPlayingBlack) {
2887                             if (!WhiteOnMove(forwardMostMove)) {
2888                                 if (first.sendTime) {
2889                                   if (first.useColors) {
2890                                     SendToProgram("white\n", &first);
2891                                   }
2892                                   SendTimeRemaining(&first, FALSE);
2893                                 }
2894                                 if (first.useColors) {
2895                                   SendToProgram("black\n", &first);
2896                                 }
2897                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898                                 first.maybeThinking = TRUE;
2899                             } else {
2900                                 if (first.usePlayother) {
2901                                   if (first.sendTime) {
2902                                     SendTimeRemaining(&first, FALSE);
2903                                   }
2904                                   SendToProgram("playother\n", &first);
2905                                   firstMove = FALSE;
2906                                 } else {
2907                                   firstMove = TRUE;
2908                                 }
2909                             }
2910                         }                       
2911                     }
2912 #endif
2913                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2914                         /* Moves came from oldmoves or moves command
2915                            while we weren't doing anything else.
2916                            */
2917                         currentMove = forwardMostMove;
2918                         ClearHighlights();/*!!could figure this out*/
2919                         flipView = appData.flipView;
2920                         DrawPosition(TRUE, boards[currentMove]);
2921                         DisplayBothClocks();
2922                         sprintf(str, "%s vs. %s",
2923                                 gameInfo.white, gameInfo.black);
2924                         DisplayTitle(str);
2925                         gameMode = IcsIdle;
2926                     } else {
2927                         /* Moves were history of an active game */
2928                         if (gameInfo.resultDetails != NULL) {
2929                             free(gameInfo.resultDetails);
2930                             gameInfo.resultDetails = NULL;
2931                         }
2932                     }
2933                     HistorySet(parseList, backwardMostMove,
2934                                forwardMostMove, currentMove-1);
2935                     DisplayMove(currentMove - 1);
2936                     if (started == STARTED_MOVES) next_out = i;
2937                     started = STARTED_NONE;
2938                     ics_getting_history = H_FALSE;
2939                     break;
2940
2941                   case STARTED_OBSERVE:
2942                     started = STARTED_NONE;
2943                     SendToICS(ics_prefix);
2944                     SendToICS("refresh\n");
2945                     break;
2946
2947                   default:
2948                     break;
2949                 }
2950                 if(bookHit) { // [HGM] book: simulate book reply
2951                     static char bookMove[MSG_SIZ]; // a bit generous?
2952
2953                     programStats.nodes = programStats.depth = programStats.time = 
2954                     programStats.score = programStats.got_only_move = 0;
2955                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956
2957                     strcpy(bookMove, "move ");
2958                     strcat(bookMove, bookHit);
2959                     HandleMachineMove(bookMove, &first);
2960                 }
2961                 continue;
2962             }
2963             
2964             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965                  started == STARTED_HOLDINGS ||
2966                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967                 /* Accumulate characters in move list or board */
2968                 parse[parse_pos++] = buf[i];
2969             }
2970             
2971             /* Start of game messages.  Mostly we detect start of game
2972                when the first board image arrives.  On some versions
2973                of the ICS, though, we need to do a "refresh" after starting
2974                to observe in order to get the current board right away. */
2975             if (looking_at(buf, &i, "Adding game * to observation list")) {
2976                 started = STARTED_OBSERVE;
2977                 continue;
2978             }
2979
2980             /* Handle auto-observe */
2981             if (appData.autoObserve &&
2982                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984                 char *player;
2985                 /* Choose the player that was highlighted, if any. */
2986                 if (star_match[0][0] == '\033' ||
2987                     star_match[1][0] != '\033') {
2988                     player = star_match[0];
2989                 } else {
2990                     player = star_match[2];
2991                 }
2992                 sprintf(str, "%sobserve %s\n",
2993                         ics_prefix, StripHighlightAndTitle(player));
2994                 SendToICS(str);
2995
2996                 /* Save ratings from notify string */
2997                 strcpy(player1Name, star_match[0]);
2998                 player1Rating = string_to_rating(star_match[1]);
2999                 strcpy(player2Name, star_match[2]);
3000                 player2Rating = string_to_rating(star_match[3]);
3001
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, 
3004                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3005                           player1Name, player1Rating,
3006                           player2Name, player2Rating);
3007
3008                 continue;
3009             }
3010
3011             /* Deal with automatic examine mode after a game,
3012                and with IcsObserving -> IcsExamining transition */
3013             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014                 looking_at(buf, &i, "has made you an examiner of game *")) {
3015
3016                 int gamenum = atoi(star_match[0]);
3017                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018                     gamenum == ics_gamenum) {
3019                     /* We were already playing or observing this game;
3020                        no need to refetch history */
3021                     gameMode = IcsExamining;
3022                     if (pausing) {
3023                         pauseExamForwardMostMove = forwardMostMove;
3024                     } else if (currentMove < forwardMostMove) {
3025                         ForwardInner(forwardMostMove);
3026                     }
3027                 } else {
3028                     /* I don't think this case really can happen */
3029                     SendToICS(ics_prefix);
3030                     SendToICS("refresh\n");
3031                 }
3032                 continue;
3033             }    
3034             
3035             /* Error messages */
3036 //          if (ics_user_moved) {
3037             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038                 if (looking_at(buf, &i, "Illegal move") ||
3039                     looking_at(buf, &i, "Not a legal move") ||
3040                     looking_at(buf, &i, "Your king is in check") ||
3041                     looking_at(buf, &i, "It isn't your turn") ||
3042                     looking_at(buf, &i, "It is not your move")) {
3043                     /* Illegal move */
3044                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045                         currentMove = --forwardMostMove;
3046                         DisplayMove(currentMove - 1); /* before DMError */
3047                         DrawPosition(FALSE, boards[currentMove]);
3048                         SwitchClocks();
3049                         DisplayBothClocks();
3050                     }
3051                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3052                     ics_user_moved = 0;
3053                     continue;
3054                 }
3055             }
3056
3057             if (looking_at(buf, &i, "still have time") ||
3058                 looking_at(buf, &i, "not out of time") ||
3059                 looking_at(buf, &i, "either player is out of time") ||
3060                 looking_at(buf, &i, "has timeseal; checking")) {
3061                 /* We must have called his flag a little too soon */
3062                 whiteFlag = blackFlag = FALSE;
3063                 continue;
3064             }
3065
3066             if (looking_at(buf, &i, "added * seconds to") ||
3067                 looking_at(buf, &i, "seconds were added to")) {
3068                 /* Update the clocks */
3069                 SendToICS(ics_prefix);
3070                 SendToICS("refresh\n");
3071                 continue;
3072             }
3073
3074             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075                 ics_clock_paused = TRUE;
3076                 StopClocks();
3077                 continue;
3078             }
3079
3080             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081                 ics_clock_paused = FALSE;
3082                 StartClocks();
3083                 continue;
3084             }
3085
3086             /* Grab player ratings from the Creating: message.
3087                Note we have to check for the special case when
3088                the ICS inserts things like [white] or [black]. */
3089             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091                 /* star_matches:
3092                    0    player 1 name (not necessarily white)
3093                    1    player 1 rating
3094                    2    empty, white, or black (IGNORED)
3095                    3    player 2 name (not necessarily black)
3096                    4    player 2 rating
3097                    
3098                    The names/ratings are sorted out when the game
3099                    actually starts (below).
3100                 */
3101                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102                 player1Rating = string_to_rating(star_match[1]);
3103                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104                 player2Rating = string_to_rating(star_match[4]);
3105
3106                 if (appData.debugMode)
3107                   fprintf(debugFP, 
3108                           "Ratings from 'Creating:' %s %d, %s %d\n",
3109                           player1Name, player1Rating,
3110                           player2Name, player2Rating);
3111
3112                 continue;
3113             }
3114             
3115             /* Improved generic start/end-of-game messages */
3116             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118                 /* If tkind == 0: */
3119                 /* star_match[0] is the game number */
3120                 /*           [1] is the white player's name */
3121                 /*           [2] is the black player's name */
3122                 /* For end-of-game: */
3123                 /*           [3] is the reason for the game end */
3124                 /*           [4] is a PGN end game-token, preceded by " " */
3125                 /* For start-of-game: */
3126                 /*           [3] begins with "Creating" or "Continuing" */
3127                 /*           [4] is " *" or empty (don't care). */
3128                 int gamenum = atoi(star_match[0]);
3129                 char *whitename, *blackname, *why, *endtoken;
3130                 ChessMove endtype = (ChessMove) 0;
3131
3132                 if (tkind == 0) {
3133                   whitename = star_match[1];
3134                   blackname = star_match[2];
3135                   why = star_match[3];
3136                   endtoken = star_match[4];
3137                 } else {
3138                   whitename = star_match[1];
3139                   blackname = star_match[3];
3140                   why = star_match[5];
3141                   endtoken = star_match[6];
3142                 }
3143
3144                 /* Game start messages */
3145                 if (strncmp(why, "Creating ", 9) == 0 ||
3146                     strncmp(why, "Continuing ", 11) == 0) {
3147                     gs_gamenum = gamenum;
3148                     strcpy(gs_kind, strchr(why, ' ') + 1);
3149 #if ZIPPY
3150                     if (appData.zippyPlay) {
3151                         ZippyGameStart(whitename, blackname);
3152                     }
3153 #endif /*ZIPPY*/
3154                     continue;
3155                 }
3156
3157                 /* Game end messages */
3158                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159                     ics_gamenum != gamenum) {
3160                     continue;
3161                 }
3162                 while (endtoken[0] == ' ') endtoken++;
3163                 switch (endtoken[0]) {
3164                   case '*':
3165                   default:
3166                     endtype = GameUnfinished;
3167                     break;
3168                   case '0':
3169                     endtype = BlackWins;
3170                     break;
3171                   case '1':
3172                     if (endtoken[1] == '/')
3173                       endtype = GameIsDrawn;
3174                     else
3175                       endtype = WhiteWins;
3176                     break;
3177                 }
3178                 GameEnds(endtype, why, GE_ICS);
3179 #if ZIPPY
3180                 if (appData.zippyPlay && first.initDone) {
3181                     ZippyGameEnd(endtype, why);
3182                     if (first.pr == NULL) {
3183                       /* Start the next process early so that we'll
3184                          be ready for the next challenge */
3185                       StartChessProgram(&first);
3186                     }
3187                     /* Send "new" early, in case this command takes
3188                        a long time to finish, so that we'll be ready
3189                        for the next challenge. */
3190                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3191                     Reset(TRUE, TRUE);
3192                 }
3193 #endif /*ZIPPY*/
3194                 continue;
3195             }
3196
3197             if (looking_at(buf, &i, "Removing game * from observation") ||
3198                 looking_at(buf, &i, "no longer observing game *") ||
3199                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200                 if (gameMode == IcsObserving &&
3201                     atoi(star_match[0]) == ics_gamenum)
3202                   {
3203                       /* icsEngineAnalyze */
3204                       if (appData.icsEngineAnalyze) {
3205                             ExitAnalyzeMode();
3206                             ModeHighlight();
3207                       }
3208                       StopClocks();
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i, "no longer examining game *")) {
3217                 if (gameMode == IcsExamining &&
3218                     atoi(star_match[0]) == ics_gamenum)
3219                   {
3220                       gameMode = IcsIdle;
3221                       ics_gamenum = -1;
3222                       ics_user_moved = FALSE;
3223                   }
3224                 continue;
3225             }
3226
3227             /* Advance leftover_start past any newlines we find,
3228                so only partial lines can get reparsed */
3229             if (looking_at(buf, &i, "\n")) {
3230                 prevColor = curColor;
3231                 if (curColor != ColorNormal) {
3232                     if (oldi > next_out) {
3233                         SendToPlayer(&buf[next_out], oldi - next_out);
3234                         next_out = oldi;
3235                     }
3236                     Colorize(ColorNormal, FALSE);
3237                     curColor = ColorNormal;
3238                 }
3239                 if (started == STARTED_BOARD) {
3240                     started = STARTED_NONE;
3241                     parse[parse_pos] = NULLCHAR;
3242                     ParseBoard12(parse);
3243                     ics_user_moved = 0;
3244
3245                     /* Send premove here */
3246                     if (appData.premove) {
3247                       char str[MSG_SIZ];
3248                       if (currentMove == 0 &&
3249                           gameMode == IcsPlayingWhite &&
3250                           appData.premoveWhite) {
3251                         sprintf(str, "%s\n", appData.premoveWhiteText);
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                         SendToICS(str);
3255                       } else if (currentMove == 1 &&
3256                                  gameMode == IcsPlayingBlack &&
3257                                  appData.premoveBlack) {
3258                         sprintf(str, "%s\n", appData.premoveBlackText);
3259                         if (appData.debugMode)
3260                           fprintf(debugFP, "Sending premove:\n");
3261                         SendToICS(str);
3262                       } else if (gotPremove) {
3263                         gotPremove = 0;
3264                         ClearPremoveHighlights();
3265                         if (appData.debugMode)
3266                           fprintf(debugFP, "Sending premove:\n");
3267                           UserMoveEvent(premoveFromX, premoveFromY, 
3268                                         premoveToX, premoveToY, 
3269                                         premovePromoChar);
3270                       }
3271                     }
3272
3273                     /* Usually suppress following prompt */
3274                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276                         if (looking_at(buf, &i, "*% ")) {
3277                             savingComment = FALSE;
3278                         }
3279                     }
3280                     next_out = i;
3281                 } else if (started == STARTED_HOLDINGS) {
3282                     int gamenum;
3283                     char new_piece[MSG_SIZ];
3284                     started = STARTED_NONE;
3285                     parse[parse_pos] = NULLCHAR;
3286                     if (appData.debugMode)
3287                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288                                                         parse, currentMove);
3289                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290                         gamenum == ics_gamenum) {
3291                         if (gameInfo.variant == VariantNormal) {
3292                           /* [HGM] We seem to switch variant during a game!
3293                            * Presumably no holdings were displayed, so we have
3294                            * to move the position two files to the right to
3295                            * create room for them!
3296                            */
3297                           VariantClass newVariant;
3298                           switch(gameInfo.boardWidth) { // base guess on board width
3299                                 case 9:  newVariant = VariantShogi; break;
3300                                 case 10: newVariant = VariantGreat; break;
3301                                 default: newVariant = VariantCrazyhouse; break;
3302                           }
3303                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304                           /* Get a move list just to see the header, which
3305                              will tell us whether this is really bug or zh */
3306                           if (ics_getting_history == H_FALSE) {
3307                             ics_getting_history = H_REQUESTED;
3308                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3309                             SendToICS(str);
3310                           }
3311                         }
3312                         new_piece[0] = NULLCHAR;
3313                         sscanf(parse, "game %d white [%s black [%s <- %s",
3314                                &gamenum, white_holding, black_holding,
3315                                new_piece);
3316                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3317                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3318                         /* [HGM] copy holdings to board holdings area */
3319                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3322 #if ZIPPY
3323                         if (appData.zippyPlay && first.initDone) {
3324                             ZippyHoldings(white_holding, black_holding,
3325                                           new_piece);
3326                         }
3327 #endif /*ZIPPY*/
3328                         if (tinyLayout || smallLayout) {
3329                             char wh[16], bh[16];
3330                             PackHolding(wh, white_holding);
3331                             PackHolding(bh, black_holding);
3332                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333                                     gameInfo.white, gameInfo.black);
3334                         } else {
3335                             sprintf(str, "%s [%s] vs. %s [%s]",
3336                                     gameInfo.white, white_holding,
3337                                     gameInfo.black, black_holding);
3338                         }
3339
3340                         DrawPosition(FALSE, boards[currentMove]);
3341                         DisplayTitle(str);
3342                     }
3343                     /* Suppress following prompt */
3344                     if (looking_at(buf, &i, "*% ")) {
3345                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346                         savingComment = FALSE;
3347                     }
3348                     next_out = i;
3349                 }
3350                 continue;
3351             }
3352
3353             i++;                /* skip unparsed character and loop back */
3354         }
3355         
3356         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357             started != STARTED_HOLDINGS && i > next_out) {
3358             SendToPlayer(&buf[next_out], i - next_out);
3359             next_out = i;
3360         }
3361         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362         
3363         leftover_len = buf_len - leftover_start;
3364         /* if buffer ends with something we couldn't parse,
3365            reparse it after appending the next read */
3366         
3367     } else if (count == 0) {
3368         RemoveInputSource(isr);
3369         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370     } else {
3371         DisplayFatalError(_("Error reading from ICS"), error, 1);
3372     }
3373 }
3374
3375
3376 /* Board style 12 looks like this:
3377    
3378    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3379    
3380  * The "<12> " is stripped before it gets to this routine.  The two
3381  * trailing 0's (flip state and clock ticking) are later addition, and
3382  * some chess servers may not have them, or may have only the first.
3383  * Additional trailing fields may be added in the future.  
3384  */
3385
3386 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3387
3388 #define RELATION_OBSERVING_PLAYED    0
3389 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE      1
3391 #define RELATION_PLAYING_NOTMYMOVE  -1
3392 #define RELATION_EXAMINING           2
3393 #define RELATION_ISOLATED_BOARD     -3
3394 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3395
3396 void
3397 ParseBoard12(string)
3398      char *string;
3399
3400     GameMode newGameMode;
3401     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404     char to_play, board_chars[200];
3405     char move_str[500], str[500], elapsed_time[500];
3406     char black[32], white[32];
3407     Board board;
3408     int prevMove = currentMove;
3409     int ticking = 2;
3410     ChessMove moveType;
3411     int fromX, fromY, toX, toY;
3412     char promoChar;
3413     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414     char *bookHit = NULL; // [HGM] book
3415     Boolean weird = FALSE, reqFlag = FALSE;
3416
3417     fromX = fromY = toX = toY = -1;
3418     
3419     newGame = FALSE;
3420
3421     if (appData.debugMode)
3422       fprintf(debugFP, _("Parsing board: %s\n"), string);
3423
3424     move_str[0] = NULLCHAR;
3425     elapsed_time[0] = NULLCHAR;
3426     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427         int  i = 0, j;
3428         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429             if(string[i] == ' ') { ranks++; files = 0; }
3430             else files++;
3431             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3432             i++;
3433         }
3434         for(j = 0; j <i; j++) board_chars[j] = string[j];
3435         board_chars[i] = '\0';
3436         string += i + 1;
3437     }
3438     n = sscanf(string, PATTERN, &to_play, &double_push,
3439                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440                &gamenum, white, black, &relation, &basetime, &increment,
3441                &white_stren, &black_stren, &white_time, &black_time,
3442                &moveNum, str, elapsed_time, move_str, &ics_flip,
3443                &ticking);
3444
3445     if (n < 21) {
3446         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447         DisplayError(str, 0);
3448         return;
3449     }
3450
3451     /* Convert the move number to internal form */
3452     moveNum = (moveNum - 1) * 2;
3453     if (to_play == 'B') moveNum++;
3454     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3456                         0, 1);
3457       return;
3458     }
3459     
3460     switch (relation) {
3461       case RELATION_OBSERVING_PLAYED:
3462       case RELATION_OBSERVING_STATIC:
3463         if (gamenum == -1) {
3464             /* Old ICC buglet */
3465             relation = RELATION_OBSERVING_STATIC;
3466         }
3467         newGameMode = IcsObserving;
3468         break;
3469       case RELATION_PLAYING_MYMOVE:
3470       case RELATION_PLAYING_NOTMYMOVE:
3471         newGameMode =
3472           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473             IcsPlayingWhite : IcsPlayingBlack;
3474         break;
3475       case RELATION_EXAMINING:
3476         newGameMode = IcsExamining;
3477         break;
3478       case RELATION_ISOLATED_BOARD:
3479       default:
3480         /* Just display this board.  If user was doing something else,
3481            we will forget about it until the next board comes. */ 
3482         newGameMode = IcsIdle;
3483         break;
3484       case RELATION_STARTING_POSITION:
3485         newGameMode = gameMode;
3486         break;
3487     }
3488     
3489     /* Modify behavior for initial board display on move listing
3490        of wild games.
3491        */
3492     switch (ics_getting_history) {
3493       case H_FALSE:
3494       case H_REQUESTED:
3495         break;
3496       case H_GOT_REQ_HEADER:
3497       case H_GOT_UNREQ_HEADER:
3498         /* This is the initial position of the current game */
3499         gamenum = ics_gamenum;
3500         moveNum = 0;            /* old ICS bug workaround */
3501         if (to_play == 'B') {
3502           startedFromSetupPosition = TRUE;
3503           blackPlaysFirst = TRUE;
3504           moveNum = 1;
3505           if (forwardMostMove == 0) forwardMostMove = 1;
3506           if (backwardMostMove == 0) backwardMostMove = 1;
3507           if (currentMove == 0) currentMove = 1;
3508         }
3509         newGameMode = gameMode;
3510         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511         break;
3512       case H_GOT_UNWANTED_HEADER:
3513         /* This is an initial board that we don't want */
3514         return;
3515       case H_GETTING_MOVES:
3516         /* Should not happen */
3517         DisplayError(_("Error gathering move list: extra board"), 0);
3518         ics_getting_history = H_FALSE;
3519         return;
3520     }
3521
3522    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3523                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524      /* [HGM] We seem to have switched variant unexpectedly
3525       * Try to guess new variant from board size
3526       */
3527           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3532           if(!weird) newVariant = VariantNormal;
3533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534           /* Get a move list just to see the header, which
3535              will tell us whether this is really bug or zh */
3536           if (ics_getting_history == H_FALSE) {
3537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3539             SendToICS(str);
3540           }
3541     }
3542     
3543     /* Take action if this is the first board of a new game, or of a
3544        different game than is currently being displayed.  */
3545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546         relation == RELATION_ISOLATED_BOARD) {
3547         
3548         /* Forget the old game and get the history (if any) of the new one */
3549         if (gameMode != BeginningOfGame) {
3550           Reset(TRUE, TRUE);
3551         }
3552         newGame = TRUE;
3553         if (appData.autoRaiseBoard) BoardToTop();
3554         prevMove = -3;
3555         if (gamenum == -1) {
3556             newGameMode = IcsIdle;
3557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558                    appData.getMoveList && !reqFlag) {
3559             /* Need to get game history */
3560             ics_getting_history = H_REQUESTED;
3561             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3562             SendToICS(str);
3563         }
3564         
3565         /* Initially flip the board to have black on the bottom if playing
3566            black or if the ICS flip flag is set, but let the user change
3567            it with the Flip View button. */
3568         flipView = appData.autoFlipView ? 
3569           (newGameMode == IcsPlayingBlack) || ics_flip :
3570           appData.flipView;
3571         
3572         /* Done with values from previous mode; copy in new ones */
3573         gameMode = newGameMode;
3574         ModeHighlight();
3575         ics_gamenum = gamenum;
3576         if (gamenum == gs_gamenum) {
3577             int klen = strlen(gs_kind);
3578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579             sprintf(str, "ICS %s", gs_kind);
3580             gameInfo.event = StrSave(str);
3581         } else {
3582             gameInfo.event = StrSave("ICS game");
3583         }
3584         gameInfo.site = StrSave(appData.icsHost);
3585         gameInfo.date = PGNDate();
3586         gameInfo.round = StrSave("-");
3587         gameInfo.white = StrSave(white);
3588         gameInfo.black = StrSave(black);
3589         timeControl = basetime * 60 * 1000;
3590         timeControl_2 = 0;
3591         timeIncrement = increment * 1000;
3592         movesPerSession = 0;
3593         gameInfo.timeControl = TimeControlTagValue();
3594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595   if (appData.debugMode) {
3596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598     setbuf(debugFP, NULL);
3599   }
3600
3601         gameInfo.outOfBook = NULL;
3602         
3603         /* Do we have the ratings? */
3604         if (strcmp(player1Name, white) == 0 &&
3605             strcmp(player2Name, black) == 0) {
3606             if (appData.debugMode)
3607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608                       player1Rating, player2Rating);
3609             gameInfo.whiteRating = player1Rating;
3610             gameInfo.blackRating = player2Rating;
3611         } else if (strcmp(player2Name, white) == 0 &&
3612                    strcmp(player1Name, black) == 0) {
3613             if (appData.debugMode)
3614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615                       player2Rating, player1Rating);
3616             gameInfo.whiteRating = player2Rating;
3617             gameInfo.blackRating = player1Rating;
3618         }
3619         player1Name[0] = player2Name[0] = NULLCHAR;
3620
3621         /* Silence shouts if requested */
3622         if (appData.quietPlay &&
3623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624             SendToICS(ics_prefix);
3625             SendToICS("set shout 0\n");
3626         }
3627     }
3628     
3629     /* Deal with midgame name changes */
3630     if (!newGame) {
3631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632             if (gameInfo.white) free(gameInfo.white);
3633             gameInfo.white = StrSave(white);
3634         }
3635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636             if (gameInfo.black) free(gameInfo.black);
3637             gameInfo.black = StrSave(black);
3638         }
3639     }
3640     
3641     /* Throw away game result if anything actually changes in examine mode */
3642     if (gameMode == IcsExamining && !newGame) {
3643         gameInfo.result = GameUnfinished;
3644         if (gameInfo.resultDetails != NULL) {
3645             free(gameInfo.resultDetails);
3646             gameInfo.resultDetails = NULL;
3647         }
3648     }
3649     
3650     /* In pausing && IcsExamining mode, we ignore boards coming
3651        in if they are in a different variation than we are. */
3652     if (pauseExamInvalid) return;
3653     if (pausing && gameMode == IcsExamining) {
3654         if (moveNum <= pauseExamForwardMostMove) {
3655             pauseExamInvalid = TRUE;
3656             forwardMostMove = pauseExamForwardMostMove;
3657             return;
3658         }
3659     }
3660     
3661   if (appData.debugMode) {
3662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663   }
3664     /* Parse the board */
3665     for (k = 0; k < ranks; k++) {
3666       for (j = 0; j < files; j++)
3667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668       if(gameInfo.holdingsWidth > 1) {
3669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3671       }
3672     }
3673     CopyBoard(boards[moveNum], board);
3674     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3675     if (moveNum == 0) {
3676         startedFromSetupPosition =
3677           !CompareBoards(board, initialPosition);
3678         if(startedFromSetupPosition)
3679             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3680     }
3681
3682     /* [HGM] Set castling rights. Take the outermost Rooks,
3683        to make it also work for FRC opening positions. Note that board12
3684        is really defective for later FRC positions, as it has no way to
3685        indicate which Rook can castle if they are on the same side of King.
3686        For the initial position we grant rights to the outermost Rooks,
3687        and remember thos rights, and we then copy them on positions
3688        later in an FRC game. This means WB might not recognize castlings with
3689        Rooks that have moved back to their original position as illegal,
3690        but in ICS mode that is not its job anyway.
3691     */
3692     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694
3695         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696             if(board[0][i] == WhiteRook) j = i;
3697         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699             if(board[0][i] == WhiteRook) j = i;
3700         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707
3708         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712             if(board[BOARD_HEIGHT-1][k] == bKing)
3713                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714     } else { int r;
3715         r = boards[moveNum][CASTLING][0] = initialRights[0];
3716         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717         r = boards[moveNum][CASTLING][1] = initialRights[1];
3718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719         r = boards[moveNum][CASTLING][3] = initialRights[3];
3720         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721         r = boards[moveNum][CASTLING][4] = initialRights[4];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723         /* wildcastle kludge: always assume King has rights */
3724         r = boards[moveNum][CASTLING][2] = initialRights[2];
3725         r = boards[moveNum][CASTLING][5] = initialRights[5];
3726     }
3727     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3729
3730     
3731     if (ics_getting_history == H_GOT_REQ_HEADER ||
3732         ics_getting_history == H_GOT_UNREQ_HEADER) {
3733         /* This was an initial position from a move list, not
3734            the current position */
3735         return;
3736     }
3737     
3738     /* Update currentMove and known move number limits */
3739     newMove = newGame || moveNum > forwardMostMove;
3740
3741     if (newGame) {
3742         forwardMostMove = backwardMostMove = currentMove = moveNum;
3743         if (gameMode == IcsExamining && moveNum == 0) {
3744           /* Workaround for ICS limitation: we are not told the wild
3745              type when starting to examine a game.  But if we ask for
3746              the move list, the move list header will tell us */
3747             ics_getting_history = H_REQUESTED;
3748             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3749             SendToICS(str);
3750         }
3751     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 #if ZIPPY
3754         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755         /* [HGM] applied this also to an engine that is silently watching        */
3756         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758             gameInfo.variant == currentlyInitializedVariant) {
3759           takeback = forwardMostMove - moveNum;
3760           for (i = 0; i < takeback; i++) {
3761             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762             SendToProgram("undo\n", &first);
3763           }
3764         }
3765 #endif
3766
3767         forwardMostMove = moveNum;
3768         if (!pausing || currentMove > forwardMostMove)
3769           currentMove = forwardMostMove;
3770     } else {
3771         /* New part of history that is not contiguous with old part */ 
3772         if (pausing && gameMode == IcsExamining) {
3773             pauseExamInvalid = TRUE;
3774             forwardMostMove = pauseExamForwardMostMove;
3775             return;
3776         }
3777         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 #if ZIPPY
3779             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780                 // [HGM] when we will receive the move list we now request, it will be
3781                 // fed to the engine from the first move on. So if the engine is not
3782                 // in the initial position now, bring it there.
3783                 InitChessProgram(&first, 0);
3784             }
3785 #endif
3786             ics_getting_history = H_REQUESTED;
3787             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3788             SendToICS(str);
3789         }
3790         forwardMostMove = backwardMostMove = currentMove = moveNum;
3791     }
3792     
3793     /* Update the clocks */
3794     if (strchr(elapsed_time, '.')) {
3795       /* Time is in ms */
3796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798     } else {
3799       /* Time is in seconds */
3800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3802     }
3803       
3804
3805 #if ZIPPY
3806     if (appData.zippyPlay && newGame &&
3807         gameMode != IcsObserving && gameMode != IcsIdle &&
3808         gameMode != IcsExamining)
3809       ZippyFirstBoard(moveNum, basetime, increment);
3810 #endif
3811     
3812     /* Put the move on the move list, first converting
3813        to canonical algebraic form. */
3814     if (moveNum > 0) {
3815   if (appData.debugMode) {
3816     if (appData.debugMode) { int f = forwardMostMove;
3817         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820     }
3821     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822     fprintf(debugFP, "moveNum = %d\n", moveNum);
3823     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824     setbuf(debugFP, NULL);
3825   }
3826         if (moveNum <= backwardMostMove) {
3827             /* We don't know what the board looked like before
3828                this move.  Punt. */
3829             strcpy(parseList[moveNum - 1], move_str);
3830             strcat(parseList[moveNum - 1], " ");
3831             strcat(parseList[moveNum - 1], elapsed_time);
3832             moveList[moveNum - 1][0] = NULLCHAR;
3833         } else if (strcmp(move_str, "none") == 0) {
3834             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835             /* Again, we don't know what the board looked like;
3836                this is really the start of the game. */
3837             parseList[moveNum - 1][0] = NULLCHAR;
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             backwardMostMove = moveNum;
3840             startedFromSetupPosition = TRUE;
3841             fromX = fromY = toX = toY = -1;
3842         } else {
3843           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3844           //                 So we parse the long-algebraic move string in stead of the SAN move
3845           int valid; char buf[MSG_SIZ], *prom;
3846
3847           // str looks something like "Q/a1-a2"; kill the slash
3848           if(str[1] == '/') 
3849                 sprintf(buf, "%c%s", str[0], str+2);
3850           else  strcpy(buf, str); // might be castling
3851           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3852                 strcat(buf, prom); // long move lacks promo specification!
3853           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854                 if(appData.debugMode) 
3855                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856                 strcpy(move_str, buf);
3857           }
3858           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859                                 &fromX, &fromY, &toX, &toY, &promoChar)
3860                || ParseOneMove(buf, moveNum - 1, &moveType,
3861                                 &fromX, &fromY, &toX, &toY, &promoChar);
3862           // end of long SAN patch
3863           if (valid) {
3864             (void) CoordsToAlgebraic(boards[moveNum - 1],
3865                                      PosFlags(moveNum - 1),
3866                                      fromY, fromX, toY, toX, promoChar,
3867                                      parseList[moveNum-1]);
3868             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3869               case MT_NONE:
3870               case MT_STALEMATE:
3871               default:
3872                 break;
3873               case MT_CHECK:
3874                 if(gameInfo.variant != VariantShogi)
3875                     strcat(parseList[moveNum - 1], "+");
3876                 break;
3877               case MT_CHECKMATE:
3878               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879                 strcat(parseList[moveNum - 1], "#");
3880                 break;
3881             }
3882             strcat(parseList[moveNum - 1], " ");
3883             strcat(parseList[moveNum - 1], elapsed_time);
3884             /* currentMoveString is set as a side-effect of ParseOneMove */
3885             strcpy(moveList[moveNum - 1], currentMoveString);
3886             strcat(moveList[moveNum - 1], "\n");
3887           } else {
3888             /* Move from ICS was illegal!?  Punt. */
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892   }
3893             strcpy(parseList[moveNum - 1], move_str);
3894             strcat(parseList[moveNum - 1], " ");
3895             strcat(parseList[moveNum - 1], elapsed_time);
3896             moveList[moveNum - 1][0] = NULLCHAR;
3897             fromX = fromY = toX = toY = -1;
3898           }
3899         }
3900   if (appData.debugMode) {
3901     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902     setbuf(debugFP, NULL);
3903   }
3904
3905 #if ZIPPY
3906         /* Send move to chess program (BEFORE animating it). */
3907         if (appData.zippyPlay && !newGame && newMove && 
3908            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909
3910             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914                             move_str);
3915                     DisplayError(str, 0);
3916                 } else {
3917                     if (first.sendTime) {
3918                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919                     }
3920                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921                     if (firstMove && !bookHit) {
3922                         firstMove = FALSE;
3923                         if (first.useColors) {
3924                           SendToProgram(gameMode == IcsPlayingWhite ?
3925                                         "white\ngo\n" :
3926                                         "black\ngo\n", &first);
3927                         } else {
3928                           SendToProgram("go\n", &first);
3929                         }
3930                         first.maybeThinking = TRUE;
3931                     }
3932                 }
3933             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934               if (moveList[moveNum - 1][0] == NULLCHAR) {
3935                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936                 DisplayError(str, 0);
3937               } else {
3938                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939                 SendMoveToProgram(moveNum - 1, &first);
3940               }
3941             }
3942         }
3943 #endif
3944     }
3945
3946     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947         /* If move comes from a remote source, animate it.  If it
3948            isn't remote, it will have already been animated. */
3949         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951         }
3952         if (!pausing && appData.highlightLastMove) {
3953             SetHighlights(fromX, fromY, toX, toY);
3954         }
3955     }
3956     
3957     /* Start the clocks */
3958     whiteFlag = blackFlag = FALSE;
3959     appData.clockMode = !(basetime == 0 && increment == 0);
3960     if (ticking == 0) {
3961       ics_clock_paused = TRUE;
3962       StopClocks();
3963     } else if (ticking == 1) {
3964       ics_clock_paused = FALSE;
3965     }
3966     if (gameMode == IcsIdle ||
3967         relation == RELATION_OBSERVING_STATIC ||
3968         relation == RELATION_EXAMINING ||
3969         ics_clock_paused)
3970       DisplayBothClocks();
3971     else
3972       StartClocks();
3973     
3974     /* Display opponents and material strengths */
3975     if (gameInfo.variant != VariantBughouse &&
3976         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977         if (tinyLayout || smallLayout) {
3978             if(gameInfo.variant == VariantNormal)
3979                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3981                     basetime, increment);
3982             else
3983                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3985                     basetime, increment, (int) gameInfo.variant);
3986         } else {
3987             if(gameInfo.variant == VariantNormal)
3988                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3990                     basetime, increment);
3991             else
3992                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3994                     basetime, increment, VariantName(gameInfo.variant));
3995         }
3996         DisplayTitle(str);
3997   if (appData.debugMode) {
3998     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3999   }
4000     }
4001
4002    
4003     /* Display the board */
4004     if (!pausing && !appData.noGUI) {
4005       
4006       if (appData.premove)
4007           if (!gotPremove || 
4008              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010               ClearPremoveHighlights();
4011
4012       DrawPosition(FALSE, boards[currentMove]);
4013       DisplayMove(moveNum - 1);
4014       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4017         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4018       }
4019     }
4020
4021     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4022 #if ZIPPY
4023     if(bookHit) { // [HGM] book: simulate book reply
4024         static char bookMove[MSG_SIZ]; // a bit generous?
4025
4026         programStats.nodes = programStats.depth = programStats.time = 
4027         programStats.score = programStats.got_only_move = 0;
4028         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4029
4030         strcpy(bookMove, "move ");
4031         strcat(bookMove, bookHit);
4032         HandleMachineMove(bookMove, &first);
4033     }
4034 #endif
4035 }
4036
4037 void
4038 GetMoveListEvent()
4039 {
4040     char buf[MSG_SIZ];
4041     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042         ics_getting_history = H_REQUESTED;
4043         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4044         SendToICS(buf);
4045     }
4046 }
4047
4048 void
4049 AnalysisPeriodicEvent(force)
4050      int force;
4051 {
4052     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053          && !force) || !appData.periodicUpdates)
4054       return;
4055
4056     /* Send . command to Crafty to collect stats */
4057     SendToProgram(".\n", &first);
4058
4059     /* Don't send another until we get a response (this makes
4060        us stop sending to old Crafty's which don't understand
4061        the "." command (sending illegal cmds resets node count & time,
4062        which looks bad)) */
4063     programStats.ok_to_send = 0;
4064 }
4065
4066 void ics_update_width(new_width)
4067         int new_width;
4068 {
4069         ics_printf("set width %d\n", new_width);
4070 }
4071
4072 void
4073 SendMoveToProgram(moveNum, cps)
4074      int moveNum;
4075      ChessProgramState *cps;
4076 {
4077     char buf[MSG_SIZ];
4078
4079     if (cps->useUsermove) {
4080       SendToProgram("usermove ", cps);
4081     }
4082     if (cps->useSAN) {
4083       char *space;
4084       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085         int len = space - parseList[moveNum];
4086         memcpy(buf, parseList[moveNum], len);
4087         buf[len++] = '\n';
4088         buf[len] = NULLCHAR;
4089       } else {
4090         sprintf(buf, "%s\n", parseList[moveNum]);
4091       }
4092       SendToProgram(buf, cps);
4093     } else {
4094       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095         AlphaRank(moveList[moveNum], 4);
4096         SendToProgram(moveList[moveNum], cps);
4097         AlphaRank(moveList[moveNum], 4); // and back
4098       } else
4099       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100        * the engine. It would be nice to have a better way to identify castle 
4101        * moves here. */
4102       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103                                                                          && cps->useOOCastle) {
4104         int fromX = moveList[moveNum][0] - AAA; 
4105         int fromY = moveList[moveNum][1] - ONE;
4106         int toX = moveList[moveNum][2] - AAA; 
4107         int toY = moveList[moveNum][3] - ONE;
4108         if((boards[moveNum][fromY][fromX] == WhiteKing 
4109             && boards[moveNum][toY][toX] == WhiteRook)
4110            || (boards[moveNum][fromY][fromX] == BlackKing 
4111                && boards[moveNum][toY][toX] == BlackRook)) {
4112           if(toX > fromX) SendToProgram("O-O\n", cps);
4113           else SendToProgram("O-O-O\n", cps);
4114         }
4115         else SendToProgram(moveList[moveNum], cps);
4116       }
4117       else SendToProgram(moveList[moveNum], cps);
4118       /* End of additions by Tord */
4119     }
4120
4121     /* [HGM] setting up the opening has brought engine in force mode! */
4122     /*       Send 'go' if we are in a mode where machine should play. */
4123     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124         (gameMode == TwoMachinesPlay   ||
4125 #ifdef ZIPPY
4126          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4127 #endif
4128          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129         SendToProgram("go\n", cps);
4130   if (appData.debugMode) {
4131     fprintf(debugFP, "(extra)\n");
4132   }
4133     }
4134     setboardSpoiledMachineBlack = 0;
4135 }
4136
4137 void
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4139      ChessMove moveType;
4140      int fromX, fromY, toX, toY;
4141 {
4142     char user_move[MSG_SIZ];
4143
4144     switch (moveType) {
4145       default:
4146         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147                 (int)moveType, fromX, fromY, toX, toY);
4148         DisplayError(user_move + strlen("say "), 0);
4149         break;
4150       case WhiteKingSideCastle:
4151       case BlackKingSideCastle:
4152       case WhiteQueenSideCastleWild:
4153       case BlackQueenSideCastleWild:
4154       /* PUSH Fabien */
4155       case WhiteHSideCastleFR:
4156       case BlackHSideCastleFR:
4157       /* POP Fabien */
4158         sprintf(user_move, "o-o\n");
4159         break;
4160       case WhiteQueenSideCastle:
4161       case BlackQueenSideCastle:
4162       case WhiteKingSideCastleWild:
4163       case BlackKingSideCastleWild:
4164       /* PUSH Fabien */
4165       case WhiteASideCastleFR:
4166       case BlackASideCastleFR:
4167       /* POP Fabien */
4168         sprintf(user_move, "o-o-o\n");
4169         break;
4170       case WhitePromotionQueen:
4171       case BlackPromotionQueen:
4172       case WhitePromotionRook:
4173       case BlackPromotionRook:
4174       case WhitePromotionBishop:
4175       case BlackPromotionBishop:
4176       case WhitePromotionKnight:
4177       case BlackPromotionKnight:
4178       case WhitePromotionKing:
4179       case BlackPromotionKing:
4180       case WhitePromotionChancellor:
4181       case BlackPromotionChancellor:
4182       case WhitePromotionArchbishop:
4183       case BlackPromotionArchbishop:
4184         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185             sprintf(user_move, "%c%c%c%c=%c\n",
4186                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187                 PieceToChar(WhiteFerz));
4188         else if(gameInfo.variant == VariantGreat)
4189             sprintf(user_move, "%c%c%c%c=%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191                 PieceToChar(WhiteMan));
4192         else
4193             sprintf(user_move, "%c%c%c%c=%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195                 PieceToChar(PromoPiece(moveType)));
4196         break;
4197       case WhiteDrop:
4198       case BlackDrop:
4199         sprintf(user_move, "%c@%c%c\n",
4200                 ToUpper(PieceToChar((ChessSquare) fromX)),
4201                 AAA + toX, ONE + toY);
4202         break;
4203       case NormalMove:
4204       case WhiteCapturesEnPassant:
4205       case BlackCapturesEnPassant:
4206       case IllegalMove:  /* could be a variant we don't quite understand */
4207         sprintf(user_move, "%c%c%c%c\n",
4208                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209         break;
4210     }
4211     SendToICS(user_move);
4212     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4214 }
4215
4216 void
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4218      int rf, ff, rt, ft;
4219      char promoChar;
4220      char move[7];
4221 {
4222     if (rf == DROP_RANK) {
4223         sprintf(move, "%c@%c%c\n",
4224                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4225     } else {
4226         if (promoChar == 'x' || promoChar == NULLCHAR) {
4227             sprintf(move, "%c%c%c%c\n",
4228                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4229         } else {
4230             sprintf(move, "%c%c%c%c%c\n",
4231                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4232         }
4233     }
4234 }
4235
4236 void
4237 ProcessICSInitScript(f)
4238      FILE *f;
4239 {
4240     char buf[MSG_SIZ];
4241
4242     while (fgets(buf, MSG_SIZ, f)) {
4243         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4244     }
4245
4246     fclose(f);
4247 }
4248
4249
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4251 void
4252 AlphaRank(char *move, int n)
4253 {
4254 //    char *p = move, c; int x, y;
4255
4256     if (appData.debugMode) {
4257         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4258     }
4259
4260     if(move[1]=='*' && 
4261        move[2]>='0' && move[2]<='9' &&
4262        move[3]>='a' && move[3]<='x'    ) {
4263         move[1] = '@';
4264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4266     } else
4267     if(move[0]>='0' && move[0]<='9' &&
4268        move[1]>='a' && move[1]<='x' &&
4269        move[2]>='0' && move[2]<='9' &&
4270        move[3]>='a' && move[3]<='x'    ) {
4271         /* input move, Shogi -> normal */
4272         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4273         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4275         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276     } else
4277     if(move[1]=='@' &&
4278        move[3]>='0' && move[3]<='9' &&
4279        move[2]>='a' && move[2]<='x'    ) {
4280         move[1] = '*';
4281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283     } else
4284     if(
4285        move[0]>='a' && move[0]<='x' &&
4286        move[3]>='0' && move[3]<='9' &&
4287        move[2]>='a' && move[2]<='x'    ) {
4288          /* output move, normal -> Shogi */
4289         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4294     }
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "   out = '%s'\n", move);
4297     }
4298 }
4299
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4301 Boolean
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303      char *move;
4304      int moveNum;
4305      ChessMove *moveType;
4306      int *fromX, *fromY, *toX, *toY;
4307      char *promoChar;
4308 {       
4309     if (appData.debugMode) {
4310         fprintf(debugFP, "move to parse: %s\n", move);
4311     }
4312     *moveType = yylexstr(moveNum, move);
4313
4314     switch (*moveType) {
4315       case WhitePromotionChancellor:
4316       case BlackPromotionChancellor:
4317       case WhitePromotionArchbishop:
4318       case BlackPromotionArchbishop:
4319       case WhitePromotionQueen:
4320       case BlackPromotionQueen:
4321       case WhitePromotionRook:
4322       case BlackPromotionRook:
4323       case WhitePromotionBishop:
4324       case BlackPromotionBishop:
4325       case WhitePromotionKnight:
4326       case BlackPromotionKnight:
4327       case WhitePromotionKing:
4328       case BlackPromotionKing:
4329       case NormalMove:
4330       case WhiteCapturesEnPassant:
4331       case BlackCapturesEnPassant:
4332       case WhiteKingSideCastle:
4333       case WhiteQueenSideCastle:
4334       case BlackKingSideCastle:
4335       case BlackQueenSideCastle:
4336       case WhiteKingSideCastleWild:
4337       case WhiteQueenSideCastleWild:
4338       case BlackKingSideCastleWild:
4339       case BlackQueenSideCastleWild:
4340       /* Code added by Tord: */
4341       case WhiteHSideCastleFR:
4342       case WhiteASideCastleFR:
4343       case BlackHSideCastleFR:
4344       case BlackASideCastleFR:
4345       /* End of code added by Tord */
4346       case IllegalMove:         /* bug or odd chess variant */
4347         *fromX = currentMoveString[0] - AAA;
4348         *fromY = currentMoveString[1] - ONE;
4349         *toX = currentMoveString[2] - AAA;
4350         *toY = currentMoveString[3] - ONE;
4351         *promoChar = currentMoveString[4];
4352         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354     if (appData.debugMode) {
4355         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4356     }
4357             *fromX = *fromY = *toX = *toY = 0;
4358             return FALSE;
4359         }
4360         if (appData.testLegality) {
4361           return (*moveType != IllegalMove);
4362         } else {
4363           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4364                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4365         }
4366
4367       case WhiteDrop:
4368       case BlackDrop:
4369         *fromX = *moveType == WhiteDrop ?
4370           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4371           (int) CharToPiece(ToLower(currentMoveString[0]));
4372         *fromY = DROP_RANK;
4373         *toX = currentMoveString[2] - AAA;
4374         *toY = currentMoveString[3] - ONE;
4375         *promoChar = NULLCHAR;
4376         return TRUE;
4377
4378       case AmbiguousMove:
4379       case ImpossibleMove:
4380       case (ChessMove) 0:       /* end of file */
4381       case ElapsedTime:
4382       case Comment:
4383       case PGNTag:
4384       case NAG:
4385       case WhiteWins:
4386       case BlackWins:
4387       case GameIsDrawn:
4388       default:
4389     if (appData.debugMode) {
4390         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4391     }
4392         /* bug? */
4393         *fromX = *fromY = *toX = *toY = 0;
4394         *promoChar = NULLCHAR;
4395         return FALSE;
4396     }
4397 }
4398
4399 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4400 // All positions will have equal probability, but the current method will not provide a unique
4401 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4402 #define DARK 1
4403 #define LITE 2
4404 #define ANY 3
4405
4406 int squaresLeft[4];
4407 int piecesLeft[(int)BlackPawn];
4408 int seed, nrOfShuffles;
4409
4410 void GetPositionNumber()
4411 {       // sets global variable seed
4412         int i;
4413
4414         seed = appData.defaultFrcPosition;
4415         if(seed < 0) { // randomize based on time for negative FRC position numbers
4416                 for(i=0; i<50; i++) seed += random();
4417                 seed = random() ^ random() >> 8 ^ random() << 8;
4418                 if(seed<0) seed = -seed;
4419         }
4420 }
4421
4422 int put(Board board, int pieceType, int rank, int n, int shade)
4423 // put the piece on the (n-1)-th empty squares of the given shade
4424 {
4425         int i;
4426
4427         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4428                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4429                         board[rank][i] = (ChessSquare) pieceType;
4430                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4431                         squaresLeft[ANY]--;
4432                         piecesLeft[pieceType]--; 
4433                         return i;
4434                 }
4435         }
4436         return -1;
4437 }
4438
4439
4440 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4441 // calculate where the next piece goes, (any empty square), and put it there
4442 {
4443         int i;
4444
4445         i = seed % squaresLeft[shade];
4446         nrOfShuffles *= squaresLeft[shade];
4447         seed /= squaresLeft[shade];
4448         put(board, pieceType, rank, i, shade);
4449 }
4450
4451 void AddTwoPieces(Board board, int pieceType, int rank)
4452 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4453 {
4454         int i, n=squaresLeft[ANY], j=n-1, k;
4455
4456         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4457         i = seed % k;  // pick one
4458         nrOfShuffles *= k;
4459         seed /= k;
4460         while(i >= j) i -= j--;
4461         j = n - 1 - j; i += j;
4462         put(board, pieceType, rank, j, ANY);
4463         put(board, pieceType, rank, i, ANY);
4464 }
4465
4466 void SetUpShuffle(Board board, int number)
4467 {
4468         int i, p, first=1;
4469
4470         GetPositionNumber(); nrOfShuffles = 1;
4471
4472         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4473         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4474         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4475
4476         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4477
4478         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4479             p = (int) board[0][i];
4480             if(p < (int) BlackPawn) piecesLeft[p] ++;
4481             board[0][i] = EmptySquare;
4482         }
4483
4484         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4485             // shuffles restricted to allow normal castling put KRR first
4486             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4487                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4488             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4489                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4490             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4491                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4492             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4493                 put(board, WhiteRook, 0, 0, ANY);
4494             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4495         }
4496
4497         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4498             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4499             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4500                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4501                 while(piecesLeft[p] >= 2) {
4502                     AddOnePiece(board, p, 0, LITE);
4503                     AddOnePiece(board, p, 0, DARK);
4504                 }
4505                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4506             }
4507
4508         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4509             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4510             // but we leave King and Rooks for last, to possibly obey FRC restriction
4511             if(p == (int)WhiteRook) continue;
4512             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4513             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4514         }
4515
4516         // now everything is placed, except perhaps King (Unicorn) and Rooks
4517
4518         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4519             // Last King gets castling rights
4520             while(piecesLeft[(int)WhiteUnicorn]) {
4521                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4522                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4523             }
4524
4525             while(piecesLeft[(int)WhiteKing]) {
4526                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4527                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4528             }
4529
4530
4531         } else {
4532             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4533             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4534         }
4535
4536         // Only Rooks can be left; simply place them all
4537         while(piecesLeft[(int)WhiteRook]) {
4538                 i = put(board, WhiteRook, 0, 0, ANY);
4539                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4540                         if(first) {
4541                                 first=0;
4542                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4543                         }
4544                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4545                 }
4546         }
4547         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4548             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4549         }
4550
4551         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4552 }
4553
4554 int SetCharTable( char *table, const char * map )
4555 /* [HGM] moved here from winboard.c because of its general usefulness */
4556 /*       Basically a safe strcpy that uses the last character as King */
4557 {
4558     int result = FALSE; int NrPieces;
4559
4560     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4561                     && NrPieces >= 12 && !(NrPieces&1)) {
4562         int i; /* [HGM] Accept even length from 12 to 34 */
4563
4564         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4565         for( i=0; i<NrPieces/2-1; i++ ) {
4566             table[i] = map[i];
4567             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4568         }
4569         table[(int) WhiteKing]  = map[NrPieces/2-1];
4570         table[(int) BlackKing]  = map[NrPieces-1];
4571
4572         result = TRUE;
4573     }
4574
4575     return result;
4576 }
4577
4578 void Prelude(Board board)
4579 {       // [HGM] superchess: random selection of exo-pieces
4580         int i, j, k; ChessSquare p; 
4581         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4582
4583         GetPositionNumber(); // use FRC position number
4584
4585         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4586             SetCharTable(pieceToChar, appData.pieceToCharTable);
4587             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4588                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4589         }
4590
4591         j = seed%4;                 seed /= 4; 
4592         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4593         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4594         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4595         j = seed%3 + (seed%3 >= j); seed /= 3; 
4596         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4597         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4598         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4599         j = seed%3;                 seed /= 3; 
4600         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4601         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4602         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4603         j = seed%2 + (seed%2 >= j); seed /= 2; 
4604         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4605         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4606         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4607         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4608         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4609         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4610         put(board, exoPieces[0],    0, 0, ANY);
4611         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4612 }
4613
4614 void
4615 InitPosition(redraw)
4616      int redraw;
4617 {
4618     ChessSquare (* pieces)[BOARD_FILES];
4619     int i, j, pawnRow, overrule,
4620     oldx = gameInfo.boardWidth,
4621     oldy = gameInfo.boardHeight,
4622     oldh = gameInfo.holdingsWidth,
4623     oldv = gameInfo.variant;
4624
4625     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4626
4627     /* [AS] Initialize pv info list [HGM] and game status */
4628     {
4629         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4630             pvInfoList[i].depth = 0;
4631             boards[i][EP_STATUS] = EP_NONE;
4632             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4633         }
4634
4635         initialRulePlies = 0; /* 50-move counter start */
4636
4637         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4638         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4639     }
4640
4641     
4642     /* [HGM] logic here is completely changed. In stead of full positions */
4643     /* the initialized data only consist of the two backranks. The switch */
4644     /* selects which one we will use, which is than copied to the Board   */
4645     /* initialPosition, which for the rest is initialized by Pawns and    */
4646     /* empty squares. This initial position is then copied to boards[0],  */
4647     /* possibly after shuffling, so that it remains available.            */
4648
4649     gameInfo.holdingsWidth = 0; /* default board sizes */
4650     gameInfo.boardWidth    = 8;
4651     gameInfo.boardHeight   = 8;
4652     gameInfo.holdingsSize  = 0;
4653     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4654     for(i=0; i<BOARD_FILES-2; i++)
4655       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4656     initialPosition[EP_STATUS] = EP_NONE;
4657     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4658
4659     switch (gameInfo.variant) {
4660     case VariantFischeRandom:
4661       shuffleOpenings = TRUE;
4662     default:
4663       pieces = FIDEArray;
4664       break;
4665     case VariantShatranj:
4666       pieces = ShatranjArray;
4667       nrCastlingRights = 0;
4668       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4669       break;
4670     case VariantTwoKings:
4671       pieces = twoKingsArray;
4672       break;
4673     case VariantCapaRandom:
4674       shuffleOpenings = TRUE;
4675     case VariantCapablanca:
4676       pieces = CapablancaArray;
4677       gameInfo.boardWidth = 10;
4678       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4679       break;
4680     case VariantGothic:
4681       pieces = GothicArray;
4682       gameInfo.boardWidth = 10;
4683       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4684       break;
4685     case VariantJanus:
4686       pieces = JanusArray;
4687       gameInfo.boardWidth = 10;
4688       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4689       nrCastlingRights = 6;
4690         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4691         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4692         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4693         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4694         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4695         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4696       break;
4697     case VariantFalcon:
4698       pieces = FalconArray;
4699       gameInfo.boardWidth = 10;
4700       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4701       break;
4702     case VariantXiangqi:
4703       pieces = XiangqiArray;
4704       gameInfo.boardWidth  = 9;
4705       gameInfo.boardHeight = 10;
4706       nrCastlingRights = 0;
4707       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4708       break;
4709     case VariantShogi:
4710       pieces = ShogiArray;
4711       gameInfo.boardWidth  = 9;
4712       gameInfo.boardHeight = 9;
4713       gameInfo.holdingsSize = 7;
4714       nrCastlingRights = 0;
4715       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4716       break;
4717     case VariantCourier:
4718       pieces = CourierArray;
4719       gameInfo.boardWidth  = 12;
4720       nrCastlingRights = 0;
4721       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4722       break;
4723     case VariantKnightmate:
4724       pieces = KnightmateArray;
4725       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4726       break;
4727     case VariantFairy:
4728       pieces = fairyArray;
4729       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4730       break;
4731     case VariantGreat:
4732       pieces = GreatArray;
4733       gameInfo.boardWidth = 10;
4734       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4735       gameInfo.holdingsSize = 8;
4736       break;
4737     case VariantSuper:
4738       pieces = FIDEArray;
4739       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4740       gameInfo.holdingsSize = 8;
4741       startedFromSetupPosition = TRUE;
4742       break;
4743     case VariantCrazyhouse:
4744     case VariantBughouse:
4745       pieces = FIDEArray;
4746       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4747       gameInfo.holdingsSize = 5;
4748       break;
4749     case VariantWildCastle:
4750       pieces = FIDEArray;
4751       /* !!?shuffle with kings guaranteed to be on d or e file */
4752       shuffleOpenings = 1;
4753       break;
4754     case VariantNoCastle:
4755       pieces = FIDEArray;
4756       nrCastlingRights = 0;
4757       /* !!?unconstrained back-rank shuffle */
4758       shuffleOpenings = 1;
4759       break;
4760     }
4761
4762     overrule = 0;
4763     if(appData.NrFiles >= 0) {
4764         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4765         gameInfo.boardWidth = appData.NrFiles;
4766     }
4767     if(appData.NrRanks >= 0) {
4768         gameInfo.boardHeight = appData.NrRanks;
4769     }
4770     if(appData.holdingsSize >= 0) {
4771         i = appData.holdingsSize;
4772         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4773         gameInfo.holdingsSize = i;
4774     }
4775     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4776     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4777         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4778
4779     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4780     if(pawnRow < 1) pawnRow = 1;
4781
4782     /* User pieceToChar list overrules defaults */
4783     if(appData.pieceToCharTable != NULL)
4784         SetCharTable(pieceToChar, appData.pieceToCharTable);
4785
4786     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4787
4788         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4789             s = (ChessSquare) 0; /* account holding counts in guard band */
4790         for( i=0; i<BOARD_HEIGHT; i++ )
4791             initialPosition[i][j] = s;
4792
4793         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4794         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4795         initialPosition[pawnRow][j] = WhitePawn;
4796         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4797         if(gameInfo.variant == VariantXiangqi) {
4798             if(j&1) {
4799                 initialPosition[pawnRow][j] = 
4800                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4801                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4802                    initialPosition[2][j] = WhiteCannon;
4803                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4804                 }
4805             }
4806         }
4807         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4808     }
4809     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4810
4811             j=BOARD_LEFT+1;
4812             initialPosition[1][j] = WhiteBishop;
4813             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4814             j=BOARD_RGHT-2;
4815             initialPosition[1][j] = WhiteRook;
4816             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4817     }
4818
4819     if( nrCastlingRights == -1) {
4820         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4821         /*       This sets default castling rights from none to normal corners   */
4822         /* Variants with other castling rights must set them themselves above    */
4823         nrCastlingRights = 6;
4824        
4825         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4826         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4827         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4828         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4829         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4830         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4831      }
4832
4833      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4834      if(gameInfo.variant == VariantGreat) { // promotion commoners
4835         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4836         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4837         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4838         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4839      }
4840   if (appData.debugMode) {
4841     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4842   }
4843     if(shuffleOpenings) {
4844         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4845         startedFromSetupPosition = TRUE;
4846     }
4847     if(startedFromPositionFile) {
4848       /* [HGM] loadPos: use PositionFile for every new game */
4849       CopyBoard(initialPosition, filePosition);
4850       for(i=0; i<nrCastlingRights; i++)
4851           initialRights[i] = filePosition[CASTLING][i];
4852       startedFromSetupPosition = TRUE;
4853     }
4854
4855     CopyBoard(boards[0], initialPosition);
4856
4857     if(oldx != gameInfo.boardWidth ||
4858        oldy != gameInfo.boardHeight ||
4859        oldh != gameInfo.holdingsWidth
4860 #ifdef GOTHIC
4861        || oldv == VariantGothic ||        // For licensing popups
4862        gameInfo.variant == VariantGothic
4863 #endif
4864 #ifdef FALCON
4865        || oldv == VariantFalcon ||
4866        gameInfo.variant == VariantFalcon
4867 #endif
4868                                          )
4869             InitDrawingSizes(-2 ,0);
4870
4871     if (redraw)
4872       DrawPosition(TRUE, boards[currentMove]);
4873 }
4874
4875 void
4876 SendBoard(cps, moveNum)
4877      ChessProgramState *cps;
4878      int moveNum;
4879 {
4880     char message[MSG_SIZ];
4881     
4882     if (cps->useSetboard) {
4883       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4884       sprintf(message, "setboard %s\n", fen);
4885       SendToProgram(message, cps);
4886       free(fen);
4887
4888     } else {
4889       ChessSquare *bp;
4890       int i, j;
4891       /* Kludge to set black to move, avoiding the troublesome and now
4892        * deprecated "black" command.
4893        */
4894       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4895
4896       SendToProgram("edit\n", cps);
4897       SendToProgram("#\n", cps);
4898       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4899         bp = &boards[moveNum][i][BOARD_LEFT];
4900         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4901           if ((int) *bp < (int) BlackPawn) {
4902             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4903                     AAA + j, ONE + i);
4904             if(message[0] == '+' || message[0] == '~') {
4905                 sprintf(message, "%c%c%c+\n",
4906                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4907                         AAA + j, ONE + i);
4908             }
4909             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4910                 message[1] = BOARD_RGHT   - 1 - j + '1';
4911                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4912             }
4913             SendToProgram(message, cps);
4914           }
4915         }
4916       }
4917     
4918       SendToProgram("c\n", cps);
4919       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4920         bp = &boards[moveNum][i][BOARD_LEFT];
4921         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4922           if (((int) *bp != (int) EmptySquare)
4923               && ((int) *bp >= (int) BlackPawn)) {
4924             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4925                     AAA + j, ONE + i);
4926             if(message[0] == '+' || message[0] == '~') {
4927                 sprintf(message, "%c%c%c+\n",
4928                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4929                         AAA + j, ONE + i);
4930             }
4931             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4932                 message[1] = BOARD_RGHT   - 1 - j + '1';
4933                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4934             }
4935             SendToProgram(message, cps);
4936           }
4937         }
4938       }
4939     
4940       SendToProgram(".\n", cps);
4941     }
4942     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4943 }
4944
4945 int
4946 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4947 {
4948     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4949     /* [HGM] add Shogi promotions */
4950     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4951     ChessSquare piece;
4952     ChessMove moveType;
4953     Boolean premove;
4954
4955     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4956     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4957
4958     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4959       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4960         return FALSE;
4961
4962     piece = boards[currentMove][fromY][fromX];
4963     if(gameInfo.variant == VariantShogi) {
4964         promotionZoneSize = 3;
4965         highestPromotingPiece = (int)WhiteFerz;
4966     }
4967
4968     // next weed out all moves that do not touch the promotion zone at all
4969     if((int)piece >= BlackPawn) {
4970         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4971              return FALSE;
4972         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4973     } else {
4974         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4975            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4976     }
4977
4978     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4979
4980     // weed out mandatory Shogi promotions
4981     if(gameInfo.variant == VariantShogi) {
4982         if(piece >= BlackPawn) {
4983             if(toY == 0 && piece == BlackPawn ||
4984                toY == 0 && piece == BlackQueen ||
4985                toY <= 1 && piece == BlackKnight) {
4986                 *promoChoice = '+';
4987                 return FALSE;
4988             }
4989         } else {
4990             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4991                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4992                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4993                 *promoChoice = '+';
4994                 return FALSE;
4995             }
4996         }
4997     }
4998
4999     // weed out obviously illegal Pawn moves
5000     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5001         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5002         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5003         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5004         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5005         // note we are not allowed to test for valid (non-)capture, due to premove
5006     }
5007
5008     // we either have a choice what to promote to, or (in Shogi) whether to promote
5009     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5010         *promoChoice = PieceToChar(BlackFerz);  // no choice
5011         return FALSE;
5012     }
5013     if(appData.alwaysPromoteToQueen) { // predetermined
5014         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5015              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5016         else *promoChoice = PieceToChar(BlackQueen);
5017         return FALSE;
5018     }
5019
5020     // suppress promotion popup on illegal moves that are not premoves
5021     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5022               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5023     if(appData.testLegality && !premove) {
5024         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5025                         fromY, fromX, toY, toX, NULLCHAR);
5026         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5027            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5028             return FALSE;
5029     }
5030
5031     return TRUE;
5032 }
5033
5034 int
5035 InPalace(row, column)
5036      int row, column;
5037 {   /* [HGM] for Xiangqi */
5038     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5039          column < (BOARD_WIDTH + 4)/2 &&
5040          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5041     return FALSE;
5042 }
5043
5044 int
5045 PieceForSquare (x, y)
5046      int x;
5047      int y;
5048 {
5049   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5050      return -1;
5051   else
5052      return boards[currentMove][y][x];
5053 }
5054
5055 int
5056 OKToStartUserMove(x, y)
5057      int x, y;
5058 {
5059     ChessSquare from_piece;
5060     int white_piece;
5061
5062     if (matchMode) return FALSE;
5063     if (gameMode == EditPosition) return TRUE;
5064
5065     if (x >= 0 && y >= 0)
5066       from_piece = boards[currentMove][y][x];
5067     else
5068       from_piece = EmptySquare;
5069
5070     if (from_piece == EmptySquare) return FALSE;
5071
5072     white_piece = (int)from_piece >= (int)WhitePawn &&
5073       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5074
5075     switch (gameMode) {
5076       case PlayFromGameFile:
5077       case AnalyzeFile:
5078       case TwoMachinesPlay:
5079       case EndOfGame:
5080         return FALSE;
5081
5082       case IcsObserving:
5083       case IcsIdle:
5084         return FALSE;
5085
5086       case MachinePlaysWhite:
5087       case IcsPlayingBlack:
5088         if (appData.zippyPlay) return FALSE;
5089         if (white_piece) {
5090             DisplayMoveError(_("You are playing Black"));
5091             return FALSE;
5092         }
5093         break;
5094
5095       case MachinePlaysBlack:
5096       case IcsPlayingWhite:
5097         if (appData.zippyPlay) return FALSE;
5098         if (!white_piece) {
5099             DisplayMoveError(_("You are playing White"));
5100             return FALSE;
5101         }
5102         break;
5103
5104       case EditGame:
5105         if (!white_piece && WhiteOnMove(currentMove)) {
5106             DisplayMoveError(_("It is White's turn"));
5107             return FALSE;
5108         }           
5109         if (white_piece && !WhiteOnMove(currentMove)) {
5110             DisplayMoveError(_("It is Black's turn"));
5111             return FALSE;
5112         }           
5113         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5114             /* Editing correspondence game history */
5115             /* Could disallow this or prompt for confirmation */
5116             cmailOldMove = -1;
5117         }
5118         break;
5119
5120       case BeginningOfGame:
5121         if (appData.icsActive) return FALSE;
5122         if (!appData.noChessProgram) {
5123             if (!white_piece) {
5124                 DisplayMoveError(_("You are playing White"));
5125                 return FALSE;
5126             }
5127         }
5128         break;
5129         
5130       case Training:
5131         if (!white_piece && WhiteOnMove(currentMove)) {
5132             DisplayMoveError(_("It is White's turn"));
5133             return FALSE;
5134         }           
5135         if (white_piece && !WhiteOnMove(currentMove)) {
5136             DisplayMoveError(_("It is Black's turn"));
5137             return FALSE;
5138         }           
5139         break;
5140
5141       default:
5142       case IcsExamining:
5143         break;
5144     }
5145     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5146         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5147         && gameMode != AnalyzeFile && gameMode != Training) {
5148         DisplayMoveError(_("Displayed position is not current"));
5149         return FALSE;
5150     }
5151     return TRUE;
5152 }
5153
5154 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5155 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5156 int lastLoadGameUseList = FALSE;
5157 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5158 ChessMove lastLoadGameStart = (ChessMove) 0;
5159
5160 ChessMove
5161 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5162      int fromX, fromY, toX, toY;
5163      int promoChar;
5164      Boolean captureOwn;
5165 {
5166     ChessMove moveType;
5167     ChessSquare pdown, pup;
5168
5169     /* Check if the user is playing in turn.  This is complicated because we
5170        let the user "pick up" a piece before it is his turn.  So the piece he
5171        tried to pick up may have been captured by the time he puts it down!
5172        Therefore we use the color the user is supposed to be playing in this
5173        test, not the color of the piece that is currently on the starting
5174        square---except in EditGame mode, where the user is playing both
5175        sides; fortunately there the capture race can't happen.  (It can
5176        now happen in IcsExamining mode, but that's just too bad.  The user
5177        will get a somewhat confusing message in that case.)
5178        */
5179
5180     switch (gameMode) {
5181       case PlayFromGameFile:
5182       case AnalyzeFile:
5183       case TwoMachinesPlay:
5184       case EndOfGame:
5185       case IcsObserving:
5186       case IcsIdle:
5187         /* We switched into a game mode where moves are not accepted,
5188            perhaps while the mouse button was down. */
5189         return ImpossibleMove;
5190
5191       case MachinePlaysWhite:
5192         /* User is moving for Black */
5193         if (WhiteOnMove(currentMove)) {
5194             DisplayMoveError(_("It is White's turn"));
5195             return ImpossibleMove;
5196         }
5197         break;
5198
5199       case MachinePlaysBlack:
5200         /* User is moving for White */
5201         if (!WhiteOnMove(currentMove)) {
5202             DisplayMoveError(_("It is Black's turn"));
5203             return ImpossibleMove;
5204         }
5205         break;
5206
5207       case EditGame:
5208       case IcsExamining:
5209       case BeginningOfGame:
5210       case AnalyzeMode:
5211       case Training:
5212         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5213             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5214             /* User is moving for Black */
5215             if (WhiteOnMove(currentMove)) {
5216                 DisplayMoveError(_("It is White's turn"));
5217                 return ImpossibleMove;
5218             }
5219         } else {
5220             /* User is moving for White */
5221             if (!WhiteOnMove(currentMove)) {
5222                 DisplayMoveError(_("It is Black's turn"));
5223                 return ImpossibleMove;
5224             }
5225         }
5226         break;
5227
5228       case IcsPlayingBlack:
5229         /* User is moving for Black */
5230         if (WhiteOnMove(currentMove)) {
5231             if (!appData.premove) {
5232                 DisplayMoveError(_("It is White's turn"));
5233             } else if (toX >= 0 && toY >= 0) {
5234                 premoveToX = toX;
5235                 premoveToY = toY;
5236                 premoveFromX = fromX;
5237                 premoveFromY = fromY;
5238                 premovePromoChar = promoChar;
5239                 gotPremove = 1;
5240                 if (appData.debugMode) 
5241                     fprintf(debugFP, "Got premove: fromX %d,"
5242                             "fromY %d, toX %d, toY %d\n",
5243                             fromX, fromY, toX, toY);
5244             }
5245             return ImpossibleMove;
5246         }
5247         break;
5248
5249       case IcsPlayingWhite:
5250         /* User is moving for White */
5251         if (!WhiteOnMove(currentMove)) {
5252             if (!appData.premove) {
5253                 DisplayMoveError(_("It is Black's turn"));
5254             } else if (toX >= 0 && toY >= 0) {
5255                 premoveToX = toX;
5256                 premoveToY = toY;
5257                 premoveFromX = fromX;
5258                 premoveFromY = fromY;
5259                 premovePromoChar = promoChar;
5260                 gotPremove = 1;
5261                 if (appData.debugMode) 
5262                     fprintf(debugFP, "Got premove: fromX %d,"
5263                             "fromY %d, toX %d, toY %d\n",
5264                             fromX, fromY, toX, toY);
5265             }
5266             return ImpossibleMove;
5267         }
5268         break;
5269
5270       default:
5271         break;
5272
5273       case EditPosition:
5274         /* EditPosition, empty square, or different color piece;
5275            click-click move is possible */
5276         if (toX == -2 || toY == -2) {
5277             boards[0][fromY][fromX] = EmptySquare;
5278             return AmbiguousMove;
5279         } else if (toX >= 0 && toY >= 0) {
5280             boards[0][toY][toX] = boards[0][fromY][fromX];
5281             boards[0][fromY][fromX] = EmptySquare;
5282             return AmbiguousMove;
5283         }
5284         return ImpossibleMove;
5285     }
5286
5287     if(toX < 0 || toY < 0) return ImpossibleMove;
5288     pdown = boards[currentMove][fromY][fromX];
5289     pup = boards[currentMove][toY][toX];
5290
5291     /* [HGM] If move started in holdings, it means a drop */
5292     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5293          if( pup != EmptySquare ) return ImpossibleMove;
5294          if(appData.testLegality) {
5295              /* it would be more logical if LegalityTest() also figured out
5296               * which drops are legal. For now we forbid pawns on back rank.
5297               * Shogi is on its own here...
5298               */
5299              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5300                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5301                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5302          }
5303          return WhiteDrop; /* Not needed to specify white or black yet */
5304     }
5305
5306     userOfferedDraw = FALSE;
5307         
5308     /* [HGM] always test for legality, to get promotion info */
5309     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5310                                          fromY, fromX, toY, toX, promoChar);
5311     /* [HGM] but possibly ignore an IllegalMove result */
5312     if (appData.testLegality) {
5313         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5314             DisplayMoveError(_("Illegal move"));
5315             return ImpossibleMove;
5316         }
5317     }
5318
5319     return moveType;
5320     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5321        function is made into one that returns an OK move type if FinishMove
5322        should be called. This to give the calling driver routine the
5323        opportunity to finish the userMove input with a promotion popup,
5324        without bothering the user with this for invalid or illegal moves */
5325
5326 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5327 }
5328
5329 /* Common tail of UserMoveEvent and DropMenuEvent */
5330 int
5331 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5332      ChessMove moveType;
5333      int fromX, fromY, toX, toY;
5334      /*char*/int promoChar;
5335 {
5336     char *bookHit = 0;
5337
5338     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5339         // [HGM] superchess: suppress promotions to non-available piece
5340         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5341         if(WhiteOnMove(currentMove)) {
5342             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5343         } else {
5344             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5345         }
5346     }
5347
5348     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5349        move type in caller when we know the move is a legal promotion */
5350     if(moveType == NormalMove && promoChar)
5351         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5352
5353     /* [HGM] convert drag-and-drop piece drops to standard form */
5354     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5355          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5356            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5357                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5358            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5359            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5360            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5361            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5362          fromY = DROP_RANK;
5363     }
5364
5365     /* [HGM] <popupFix> The following if has been moved here from
5366        UserMoveEvent(). Because it seemed to belong here (why not allow
5367        piece drops in training games?), and because it can only be
5368        performed after it is known to what we promote. */
5369     if (gameMode == Training) {
5370       /* compare the move played on the board to the next move in the
5371        * game. If they match, display the move and the opponent's response. 
5372        * If they don't match, display an error message.
5373        */
5374       int saveAnimate;
5375       Board testBoard;
5376       CopyBoard(testBoard, boards[currentMove]);
5377       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5378
5379       if (CompareBoards(testBoard, boards[currentMove+1])) {
5380         ForwardInner(currentMove+1);
5381
5382         /* Autoplay the opponent's response.
5383          * if appData.animate was TRUE when Training mode was entered,
5384          * the response will be animated.
5385          */
5386         saveAnimate = appData.animate;
5387         appData.animate = animateTraining;
5388         ForwardInner(currentMove+1);
5389         appData.animate = saveAnimate;
5390
5391         /* check for the end of the game */
5392         if (currentMove >= forwardMostMove) {
5393           gameMode = PlayFromGameFile;
5394           ModeHighlight();
5395           SetTrainingModeOff();
5396           DisplayInformation(_("End of game"));
5397         }
5398       } else {
5399         DisplayError(_("Incorrect move"), 0);
5400       }
5401       return 1;
5402     }
5403
5404   /* Ok, now we know that the move is good, so we can kill
5405      the previous line in Analysis Mode */
5406   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5407                                 && currentMove < forwardMostMove) {
5408     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5409   }
5410
5411   /* If we need the chess program but it's dead, restart it */
5412   ResurrectChessProgram();
5413
5414   /* A user move restarts a paused game*/
5415   if (pausing)
5416     PauseEvent();
5417
5418   thinkOutput[0] = NULLCHAR;
5419
5420   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5421
5422   if (gameMode == BeginningOfGame) {
5423     if (appData.noChessProgram) {
5424       gameMode = EditGame;
5425       SetGameInfo();
5426     } else {
5427       char buf[MSG_SIZ];
5428       gameMode = MachinePlaysBlack;
5429       StartClocks();
5430       SetGameInfo();
5431       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5432       DisplayTitle(buf);
5433       if (first.sendName) {
5434         sprintf(buf, "name %s\n", gameInfo.white);
5435         SendToProgram(buf, &first);
5436       }
5437       StartClocks();
5438     }
5439     ModeHighlight();
5440   }
5441
5442   /* Relay move to ICS or chess engine */
5443   if (appData.icsActive) {
5444     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5445         gameMode == IcsExamining) {
5446       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5447       ics_user_moved = 1;
5448     }
5449   } else {
5450     if (first.sendTime && (gameMode == BeginningOfGame ||
5451                            gameMode == MachinePlaysWhite ||
5452                            gameMode == MachinePlaysBlack)) {
5453       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5454     }
5455     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5456          // [HGM] book: if program might be playing, let it use book
5457         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5458         first.maybeThinking = TRUE;
5459     } else SendMoveToProgram(forwardMostMove-1, &first);
5460     if (currentMove == cmailOldMove + 1) {
5461       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5462     }
5463   }
5464
5465   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5466
5467   switch (gameMode) {
5468   case EditGame:
5469     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5470     case MT_NONE:
5471     case MT_CHECK:
5472       break;
5473     case MT_CHECKMATE:
5474     case MT_STAINMATE:
5475       if (WhiteOnMove(currentMove)) {
5476         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5477       } else {
5478         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5479       }
5480       break;
5481     case MT_STALEMATE:
5482       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5483       break;
5484     }
5485     break;
5486     
5487   case MachinePlaysBlack:
5488   case MachinePlaysWhite:
5489     /* disable certain menu options while machine is thinking */
5490     SetMachineThinkingEnables();
5491     break;
5492
5493   default:
5494     break;
5495   }
5496
5497   if(bookHit) { // [HGM] book: simulate book reply
5498         static char bookMove[MSG_SIZ]; // a bit generous?
5499
5500         programStats.nodes = programStats.depth = programStats.time = 
5501         programStats.score = programStats.got_only_move = 0;
5502         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5503
5504         strcpy(bookMove, "move ");
5505         strcat(bookMove, bookHit);
5506         HandleMachineMove(bookMove, &first);
5507   }
5508   return 1;
5509 }
5510
5511 void
5512 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5513      int fromX, fromY, toX, toY;
5514      int promoChar;
5515 {
5516     /* [HGM] This routine was added to allow calling of its two logical
5517        parts from other modules in the old way. Before, UserMoveEvent()
5518        automatically called FinishMove() if the move was OK, and returned
5519        otherwise. I separated the two, in order to make it possible to
5520        slip a promotion popup in between. But that it always needs two
5521        calls, to the first part, (now called UserMoveTest() ), and to
5522        FinishMove if the first part succeeded. Calls that do not need
5523        to do anything in between, can call this routine the old way. 
5524     */
5525     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5526 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5527     if(moveType == AmbiguousMove)
5528         DrawPosition(FALSE, boards[currentMove]);
5529     else if(moveType != ImpossibleMove && moveType != Comment)
5530         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5531 }
5532
5533 void LeftClick(ClickType clickType, int xPix, int yPix)
5534 {
5535     int x, y;
5536     Boolean saveAnimate;
5537     static int second = 0, promotionChoice = 0;
5538     char promoChoice = NULLCHAR;
5539
5540     if (clickType == Press) ErrorPopDown();
5541
5542     x = EventToSquare(xPix, BOARD_WIDTH);
5543     y = EventToSquare(yPix, BOARD_HEIGHT);
5544     if (!flipView && y >= 0) {
5545         y = BOARD_HEIGHT - 1 - y;
5546     }
5547     if (flipView && x >= 0) {
5548         x = BOARD_WIDTH - 1 - x;
5549     }
5550
5551     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5552         if(clickType == Release) return; // ignore upclick of click-click destination
5553         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5554         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5555         if(gameInfo.holdingsWidth && 
5556                 (WhiteOnMove(currentMove) 
5557                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5558                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5559             // click in right holdings, for determining promotion piece
5560             ChessSquare p = boards[currentMove][y][x];
5561             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5562             if(p != EmptySquare) {
5563                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5564                 fromX = fromY = -1;
5565                 return;
5566             }
5567         }
5568         DrawPosition(FALSE, boards[currentMove]);
5569         return;
5570     }
5571
5572     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5573     if(clickType == Press
5574             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5575               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5576               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5577         return;
5578
5579     if (fromX == -1) {
5580         if (clickType == Press) {
5581             /* First square */
5582             if (OKToStartUserMove(x, y)) {
5583                 fromX = x;
5584                 fromY = y;
5585                 second = 0;
5586                 DragPieceBegin(xPix, yPix);
5587                 if (appData.highlightDragging) {
5588                     SetHighlights(x, y, -1, -1);
5589                 }
5590             }
5591         }
5592         return;
5593     }
5594
5595     /* fromX != -1 */
5596     if (clickType == Press && gameMode != EditPosition) {
5597         ChessSquare fromP;
5598         ChessSquare toP;
5599         int frc;
5600
5601         // ignore off-board to clicks
5602         if(y < 0 || x < 0) return;
5603
5604         /* Check if clicking again on the same color piece */
5605         fromP = boards[currentMove][fromY][fromX];
5606         toP = boards[currentMove][y][x];
5607         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5608         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5609              WhitePawn <= toP && toP <= WhiteKing &&
5610              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5611              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5612             (BlackPawn <= fromP && fromP <= BlackKing && 
5613              BlackPawn <= toP && toP <= BlackKing &&
5614              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5615              !(fromP == BlackKing && toP == BlackRook && frc))) {
5616             /* Clicked again on same color piece -- changed his mind */
5617             second = (x == fromX && y == fromY);
5618             if (appData.highlightDragging) {
5619                 SetHighlights(x, y, -1, -1);
5620             } else {
5621                 ClearHighlights();
5622             }
5623             if (OKToStartUserMove(x, y)) {
5624                 fromX = x;
5625                 fromY = y;
5626                 DragPieceBegin(xPix, yPix);
5627             }
5628             return;
5629         }
5630         // ignore clicks on holdings
5631         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5632     }
5633
5634     if (clickType == Release && x == fromX && y == fromY) {
5635         DragPieceEnd(xPix, yPix);
5636         if (appData.animateDragging) {
5637             /* Undo animation damage if any */
5638             DrawPosition(FALSE, NULL);
5639         }
5640         if (second) {
5641             /* Second up/down in same square; just abort move */
5642             second = 0;
5643             fromX = fromY = -1;
5644             ClearHighlights();
5645             gotPremove = 0;
5646             ClearPremoveHighlights();
5647         } else {
5648             /* First upclick in same square; start click-click mode */
5649             SetHighlights(x, y, -1, -1);
5650         }
5651         return;
5652     }
5653
5654     /* we now have a different from- and (possibly off-board) to-square */
5655     /* Completed move */
5656     toX = x;
5657     toY = y;
5658     saveAnimate = appData.animate;
5659     if (clickType == Press) {
5660         /* Finish clickclick move */
5661         if (appData.animate || appData.highlightLastMove) {
5662             SetHighlights(fromX, fromY, toX, toY);
5663         } else {
5664             ClearHighlights();
5665         }
5666     } else {
5667         /* Finish drag move */
5668         if (appData.highlightLastMove) {
5669             SetHighlights(fromX, fromY, toX, toY);
5670         } else {
5671             ClearHighlights();
5672         }
5673         DragPieceEnd(xPix, yPix);
5674         /* Don't animate move and drag both */
5675         appData.animate = FALSE;
5676     }
5677
5678     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5679     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5680         ClearHighlights();
5681         fromX = fromY = -1;
5682         DrawPosition(TRUE, NULL);
5683         return;
5684     }
5685
5686     // off-board moves should not be highlighted
5687     if(x < 0 || x < 0) ClearHighlights();
5688
5689     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5690         SetHighlights(fromX, fromY, toX, toY);
5691         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5692             // [HGM] super: promotion to captured piece selected from holdings
5693             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5694             promotionChoice = TRUE;
5695             // kludge follows to temporarily execute move on display, without promoting yet
5696             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5697             boards[currentMove][toY][toX] = p;
5698             DrawPosition(FALSE, boards[currentMove]);
5699             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5700             boards[currentMove][toY][toX] = q;
5701             DisplayMessage("Click in holdings to choose piece", "");
5702             return;
5703         }
5704         PromotionPopUp();
5705     } else {
5706         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5707         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5708         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5709         fromX = fromY = -1;
5710     }
5711     appData.animate = saveAnimate;
5712     if (appData.animate || appData.animateDragging) {
5713         /* Undo animation damage if needed */
5714         DrawPosition(FALSE, NULL);
5715     }
5716 }
5717
5718 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5719 {
5720 //    char * hint = lastHint;
5721     FrontEndProgramStats stats;
5722
5723     stats.which = cps == &first ? 0 : 1;
5724     stats.depth = cpstats->depth;
5725     stats.nodes = cpstats->nodes;
5726     stats.score = cpstats->score;
5727     stats.time = cpstats->time;
5728     stats.pv = cpstats->movelist;
5729     stats.hint = lastHint;
5730     stats.an_move_index = 0;
5731     stats.an_move_count = 0;
5732
5733     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5734         stats.hint = cpstats->move_name;
5735         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5736         stats.an_move_count = cpstats->nr_moves;
5737     }
5738
5739     SetProgramStats( &stats );
5740 }
5741
5742 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5743 {   // [HGM] book: this routine intercepts moves to simulate book replies
5744     char *bookHit = NULL;
5745
5746     //first determine if the incoming move brings opponent into his book
5747     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5748         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5749     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5750     if(bookHit != NULL && !cps->bookSuspend) {
5751         // make sure opponent is not going to reply after receiving move to book position
5752         SendToProgram("force\n", cps);
5753         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5754     }
5755     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5756     // now arrange restart after book miss
5757     if(bookHit) {
5758         // after a book hit we never send 'go', and the code after the call to this routine
5759         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5760         char buf[MSG_SIZ];
5761         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5762         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5763         SendToProgram(buf, cps);
5764         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5765     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5766         SendToProgram("go\n", cps);
5767         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5768     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5769         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5770             SendToProgram("go\n", cps); 
5771         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5772     }
5773     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5774 }
5775
5776 char *savedMessage;
5777 ChessProgramState *savedState;
5778 void DeferredBookMove(void)
5779 {
5780         if(savedState->lastPing != savedState->lastPong)
5781                     ScheduleDelayedEvent(DeferredBookMove, 10);
5782         else
5783         HandleMachineMove(savedMessage, savedState);
5784 }
5785
5786 void
5787 HandleMachineMove(message, cps)
5788      char *message;
5789      ChessProgramState *cps;
5790 {
5791     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5792     char realname[MSG_SIZ];
5793     int fromX, fromY, toX, toY;
5794     ChessMove moveType;
5795     char promoChar;
5796     char *p;
5797     int machineWhite;
5798     char *bookHit;
5799
5800     cps->userError = 0;
5801
5802 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5803     /*
5804      * Kludge to ignore BEL characters
5805      */
5806     while (*message == '\007') message++;
5807
5808     /*
5809      * [HGM] engine debug message: ignore lines starting with '#' character
5810      */
5811     if(cps->debug && *message == '#') return;
5812
5813     /*
5814      * Look for book output
5815      */
5816     if (cps == &first && bookRequested) {
5817         if (message[0] == '\t' || message[0] == ' ') {
5818             /* Part of the book output is here; append it */
5819             strcat(bookOutput, message);
5820             strcat(bookOutput, "  \n");
5821             return;
5822         } else if (bookOutput[0] != NULLCHAR) {
5823             /* All of book output has arrived; display it */
5824             char *p = bookOutput;
5825             while (*p != NULLCHAR) {
5826                 if (*p == '\t') *p = ' ';
5827                 p++;
5828             }
5829             DisplayInformation(bookOutput);
5830             bookRequested = FALSE;
5831             /* Fall through to parse the current output */
5832         }
5833     }
5834
5835     /*
5836      * Look for machine move.
5837      */
5838     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5839         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5840     {
5841         /* This method is only useful on engines that support ping */
5842         if (cps->lastPing != cps->lastPong) {
5843           if (gameMode == BeginningOfGame) {
5844             /* Extra move from before last new; ignore */
5845             if (appData.debugMode) {
5846                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5847             }
5848           } else {
5849             if (appData.debugMode) {
5850                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5851                         cps->which, gameMode);
5852             }
5853
5854             SendToProgram("undo\n", cps);
5855           }
5856           return;
5857         }
5858
5859         switch (gameMode) {
5860           case BeginningOfGame:
5861             /* Extra move from before last reset; ignore */
5862             if (appData.debugMode) {
5863                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5864             }
5865             return;
5866
5867           case EndOfGame:
5868           case IcsIdle:
5869           default:
5870             /* Extra move after we tried to stop.  The mode test is
5871                not a reliable way of detecting this problem, but it's
5872                the best we can do on engines that don't support ping.
5873             */
5874             if (appData.debugMode) {
5875                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5876                         cps->which, gameMode);
5877             }
5878             SendToProgram("undo\n", cps);
5879             return;
5880
5881           case MachinePlaysWhite:
5882           case IcsPlayingWhite:
5883             machineWhite = TRUE;
5884             break;
5885
5886           case MachinePlaysBlack:
5887           case IcsPlayingBlack:
5888             machineWhite = FALSE;
5889             break;
5890
5891           case TwoMachinesPlay:
5892             machineWhite = (cps->twoMachinesColor[0] == 'w');
5893             break;
5894         }
5895         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5896             if (appData.debugMode) {
5897                 fprintf(debugFP,
5898                         "Ignoring move out of turn by %s, gameMode %d"
5899                         ", forwardMost %d\n",
5900                         cps->which, gameMode, forwardMostMove);
5901             }
5902             return;
5903         }
5904
5905     if (appData.debugMode) { int f = forwardMostMove;
5906         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5907                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5908                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5909     }
5910         if(cps->alphaRank) AlphaRank(machineMove, 4);
5911         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5912                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5913             /* Machine move could not be parsed; ignore it. */
5914             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5915                     machineMove, cps->which);
5916             DisplayError(buf1, 0);
5917             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5918                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5919             if (gameMode == TwoMachinesPlay) {
5920               GameEnds(machineWhite ? BlackWins : WhiteWins,
5921                        buf1, GE_XBOARD);
5922             }
5923             return;
5924         }
5925
5926         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5927         /* So we have to redo legality test with true e.p. status here,  */
5928         /* to make sure an illegal e.p. capture does not slip through,   */
5929         /* to cause a forfeit on a justified illegal-move complaint      */
5930         /* of the opponent.                                              */
5931         if( gameMode==TwoMachinesPlay && appData.testLegality
5932             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5933                                                               ) {
5934            ChessMove moveType;
5935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5936                              fromY, fromX, toY, toX, promoChar);
5937             if (appData.debugMode) {
5938                 int i;
5939                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5940                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5941                 fprintf(debugFP, "castling rights\n");
5942             }
5943             if(moveType == IllegalMove) {
5944                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5946                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5947                            buf1, GE_XBOARD);
5948                 return;
5949            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5950            /* [HGM] Kludge to handle engines that send FRC-style castling
5951               when they shouldn't (like TSCP-Gothic) */
5952            switch(moveType) {
5953              case WhiteASideCastleFR:
5954              case BlackASideCastleFR:
5955                toX+=2;
5956                currentMoveString[2]++;
5957                break;
5958              case WhiteHSideCastleFR:
5959              case BlackHSideCastleFR:
5960                toX--;
5961                currentMoveString[2]--;
5962                break;
5963              default: ; // nothing to do, but suppresses warning of pedantic compilers
5964            }
5965         }
5966         hintRequested = FALSE;
5967         lastHint[0] = NULLCHAR;
5968         bookRequested = FALSE;
5969         /* Program may be pondering now */
5970         cps->maybeThinking = TRUE;
5971         if (cps->sendTime == 2) cps->sendTime = 1;
5972         if (cps->offeredDraw) cps->offeredDraw--;
5973
5974 #if ZIPPY
5975         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5976             first.initDone) {
5977           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5978           ics_user_moved = 1;
5979           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5980                 char buf[3*MSG_SIZ];
5981
5982                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5983                         programStats.score / 100.,
5984                         programStats.depth,
5985                         programStats.time / 100.,
5986                         (unsigned int)programStats.nodes,
5987                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5988                         programStats.movelist);
5989                 SendToICS(buf);
5990 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5991           }
5992         }
5993 #endif
5994         /* currentMoveString is set as a side-effect of ParseOneMove */
5995         strcpy(machineMove, currentMoveString);
5996         strcat(machineMove, "\n");
5997         strcpy(moveList[forwardMostMove], machineMove);
5998
5999         /* [AS] Save move info and clear stats for next move */
6000         pvInfoList[ forwardMostMove ].score = programStats.score;
6001         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6002         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6003         ClearProgramStats();
6004         thinkOutput[0] = NULLCHAR;
6005         hiddenThinkOutputState = 0;
6006
6007         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6008
6009         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6010         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6011             int count = 0;
6012
6013             while( count < adjudicateLossPlies ) {
6014                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6015
6016                 if( count & 1 ) {
6017                     score = -score; /* Flip score for winning side */
6018                 }
6019
6020                 if( score > adjudicateLossThreshold ) {
6021                     break;
6022                 }
6023
6024                 count++;
6025             }
6026
6027             if( count >= adjudicateLossPlies ) {
6028                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6029
6030                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6031                     "Xboard adjudication", 
6032                     GE_XBOARD );
6033
6034                 return;
6035             }
6036         }
6037
6038         if( gameMode == TwoMachinesPlay ) {
6039           // [HGM] some adjudications useful with buggy engines
6040             int k, count = 0; static int bare = 1;
6041           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6042
6043
6044             if( appData.testLegality )
6045             {   /* [HGM] Some more adjudications for obstinate engines */
6046                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6047                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6048                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6049                 static int moveCount = 6;
6050                 ChessMove result;
6051                 char *reason = NULL;
6052
6053                 /* Count what is on board. */
6054                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6055                 {   ChessSquare p = boards[forwardMostMove][i][j];
6056                     int m=i;
6057
6058                     switch((int) p)
6059                     {   /* count B,N,R and other of each side */
6060                         case WhiteKing:
6061                         case BlackKing:
6062                              NrK++; break; // [HGM] atomic: count Kings
6063                         case WhiteKnight:
6064                              NrWN++; break;
6065                         case WhiteBishop:
6066                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6067                              bishopsColor |= 1 << ((i^j)&1);
6068                              NrWB++; break;
6069                         case BlackKnight:
6070                              NrBN++; break;
6071                         case BlackBishop:
6072                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6073                              bishopsColor |= 1 << ((i^j)&1);
6074                              NrBB++; break;
6075                         case WhiteRook:
6076                              NrWR++; break;
6077                         case BlackRook:
6078                              NrBR++; break;
6079                         case WhiteQueen:
6080                              NrWQ++; break;
6081                         case BlackQueen:
6082                              NrBQ++; break;
6083                         case EmptySquare: 
6084                              break;
6085                         case BlackPawn:
6086                              m = 7-i;
6087                         case WhitePawn:
6088                              PawnAdvance += m; NrPawns++;
6089                     }
6090                     NrPieces += (p != EmptySquare);
6091                     NrW += ((int)p < (int)BlackPawn);
6092                     if(gameInfo.variant == VariantXiangqi && 
6093                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6094                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6095                         NrW -= ((int)p < (int)BlackPawn);
6096                     }
6097                 }
6098
6099                 /* Some material-based adjudications that have to be made before stalemate test */
6100                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6101                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6102                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6103                      if(appData.checkMates) {
6104                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6105                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6106                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6107                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6108                          return;
6109                      }
6110                 }
6111
6112                 /* Bare King in Shatranj (loses) or Losers (wins) */
6113                 if( NrW == 1 || NrPieces - NrW == 1) {
6114                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6115                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6116                      if(appData.checkMates) {
6117                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6118                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6119                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6120                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6121                          return;
6122                      }
6123                   } else
6124                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6125                   {    /* bare King */
6126                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6127                         if(appData.checkMates) {
6128                             /* but only adjudicate if adjudication enabled */
6129                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6130                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6131                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6132                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6133                             return;
6134                         }
6135                   }
6136                 } else bare = 1;
6137
6138
6139             // don't wait for engine to announce game end if we can judge ourselves
6140             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6141               case MT_CHECK:
6142                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6143                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6144                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6145                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6146                             checkCnt++;
6147                         if(checkCnt >= 2) {
6148                             reason = "Xboard adjudication: 3rd check";
6149                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6150                             break;
6151                         }
6152                     }
6153                 }
6154               case MT_NONE:
6155               default:
6156                 break;
6157               case MT_STALEMATE:
6158               case MT_STAINMATE:
6159                 reason = "Xboard adjudication: Stalemate";
6160                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6161                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6162                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6163                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6164                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6165                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6166                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6167                                                                         EP_CHECKMATE : EP_WINS);
6168                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6169                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6170                 }
6171                 break;
6172               case MT_CHECKMATE:
6173                 reason = "Xboard adjudication: Checkmate";
6174                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6175                 break;
6176             }
6177
6178                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6179                     case EP_STALEMATE:
6180                         result = GameIsDrawn; break;
6181                     case EP_CHECKMATE:
6182                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6183                     case EP_WINS:
6184                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6185                     default:
6186                         result = (ChessMove) 0;
6187                 }
6188                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6189                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6190                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6191                     GameEnds( result, reason, GE_XBOARD );
6192                     return;
6193                 }
6194
6195                 /* Next absolutely insufficient mating material. */
6196                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6197                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6198                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6199                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6200                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6201
6202                      /* always flag draws, for judging claims */
6203                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6204
6205                      if(appData.materialDraws) {
6206                          /* but only adjudicate them if adjudication enabled */
6207                          SendToProgram("force\n", cps->other); // suppress reply
6208                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6209                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6210                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6211                          return;
6212                      }
6213                 }
6214
6215                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6216                 if(NrPieces == 4 && 
6217                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6218                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6219                    || NrWN==2 || NrBN==2     /* KNNK */
6220                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6221                   ) ) {
6222                      if(--moveCount < 0 && appData.trivialDraws)
6223                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6224                           SendToProgram("force\n", cps->other); // suppress reply
6225                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6226                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6227                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6228                           return;
6229                      }
6230                 } else moveCount = 6;
6231             }
6232           }
6233           
6234           if (appData.debugMode) { int i;
6235             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6236                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6237                     appData.drawRepeats);
6238             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6239               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6240             
6241           }
6242
6243                 /* Check for rep-draws */
6244                 count = 0;
6245                 for(k = forwardMostMove-2;
6246                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6247                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6248                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6249                     k-=2)
6250                 {   int rights=0;
6251                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6252                         /* compare castling rights */
6253                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6254                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6255                                 rights++; /* King lost rights, while rook still had them */
6256                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6257                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6258                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6259                                    rights++; /* but at least one rook lost them */
6260                         }
6261                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6262                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6263                                 rights++; 
6264                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6265                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6266                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6267                                    rights++;
6268                         }
6269                         if( rights == 0 && ++count > appData.drawRepeats-2
6270                             && appData.drawRepeats > 1) {
6271                              /* adjudicate after user-specified nr of repeats */
6272                              SendToProgram("force\n", cps->other); // suppress reply
6273                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6274                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6276                                 // [HGM] xiangqi: check for forbidden perpetuals
6277                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6278                                 for(m=forwardMostMove; m>k; m-=2) {
6279                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6280                                         ourPerpetual = 0; // the current mover did not always check
6281                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6282                                         hisPerpetual = 0; // the opponent did not always check
6283                                 }
6284                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6285                                                                         ourPerpetual, hisPerpetual);
6286                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6287                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6288                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6289                                     return;
6290                                 }
6291                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6292                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6293                                 // Now check for perpetual chases
6294                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6295                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6296                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6297                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6298                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6299                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6300                                         return;
6301                                     }
6302                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6303                                         break; // Abort repetition-checking loop.
6304                                 }
6305                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6306                              }
6307                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6308                              return;
6309                         }
6310                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6311                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6312                     }
6313                 }
6314
6315                 /* Now we test for 50-move draws. Determine ply count */
6316                 count = forwardMostMove;
6317                 /* look for last irreversble move */
6318                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6319                     count--;
6320                 /* if we hit starting position, add initial plies */
6321                 if( count == backwardMostMove )
6322                     count -= initialRulePlies;
6323                 count = forwardMostMove - count; 
6324                 if( count >= 100)
6325                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6326                          /* this is used to judge if draw claims are legal */
6327                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6328                          SendToProgram("force\n", cps->other); // suppress reply
6329                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6330                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6331                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6332                          return;
6333                 }
6334
6335                 /* if draw offer is pending, treat it as a draw claim
6336                  * when draw condition present, to allow engines a way to
6337                  * claim draws before making their move to avoid a race
6338                  * condition occurring after their move
6339                  */
6340                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6341                          char *p = NULL;
6342                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6343                              p = "Draw claim: 50-move rule";
6344                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6345                              p = "Draw claim: 3-fold repetition";
6346                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6347                              p = "Draw claim: insufficient mating material";
6348                          if( p != NULL ) {
6349                              SendToProgram("force\n", cps->other); // suppress reply
6350                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6351                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6352                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6353                              return;
6354                          }
6355                 }
6356
6357
6358                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6359                     SendToProgram("force\n", cps->other); // suppress reply
6360                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6361                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6362
6363                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6364
6365                     return;
6366                 }
6367         }
6368
6369         bookHit = NULL;
6370         if (gameMode == TwoMachinesPlay) {
6371             /* [HGM] relaying draw offers moved to after reception of move */
6372             /* and interpreting offer as claim if it brings draw condition */
6373             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6374                 SendToProgram("draw\n", cps->other);
6375             }
6376             if (cps->other->sendTime) {
6377                 SendTimeRemaining(cps->other,
6378                                   cps->other->twoMachinesColor[0] == 'w');
6379             }
6380             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6381             if (firstMove && !bookHit) {
6382                 firstMove = FALSE;
6383                 if (cps->other->useColors) {
6384                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6385                 }
6386                 SendToProgram("go\n", cps->other);
6387             }
6388             cps->other->maybeThinking = TRUE;
6389         }
6390
6391         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6392         
6393         if (!pausing && appData.ringBellAfterMoves) {
6394             RingBell();
6395         }
6396
6397         /* 
6398          * Reenable menu items that were disabled while
6399          * machine was thinking
6400          */
6401         if (gameMode != TwoMachinesPlay)
6402             SetUserThinkingEnables();
6403
6404         // [HGM] book: after book hit opponent has received move and is now in force mode
6405         // force the book reply into it, and then fake that it outputted this move by jumping
6406         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6407         if(bookHit) {
6408                 static char bookMove[MSG_SIZ]; // a bit generous?
6409
6410                 strcpy(bookMove, "move ");
6411                 strcat(bookMove, bookHit);
6412                 message = bookMove;
6413                 cps = cps->other;
6414                 programStats.nodes = programStats.depth = programStats.time = 
6415                 programStats.score = programStats.got_only_move = 0;
6416                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6417
6418                 if(cps->lastPing != cps->lastPong) {
6419                     savedMessage = message; // args for deferred call
6420                     savedState = cps;
6421                     ScheduleDelayedEvent(DeferredBookMove, 10);
6422                     return;
6423                 }
6424                 goto FakeBookMove;
6425         }
6426
6427         return;
6428     }
6429
6430     /* Set special modes for chess engines.  Later something general
6431      *  could be added here; for now there is just one kludge feature,
6432      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6433      *  when "xboard" is given as an interactive command.
6434      */
6435     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6436         cps->useSigint = FALSE;
6437         cps->useSigterm = FALSE;
6438     }
6439     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6440       ParseFeatures(message+8, cps);
6441       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6442     }
6443
6444     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6445      * want this, I was asked to put it in, and obliged.
6446      */
6447     if (!strncmp(message, "setboard ", 9)) {
6448         Board initial_position;
6449
6450         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6451
6452         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6453             DisplayError(_("Bad FEN received from engine"), 0);
6454             return ;
6455         } else {
6456            Reset(TRUE, FALSE);
6457            CopyBoard(boards[0], initial_position);
6458            initialRulePlies = FENrulePlies;
6459            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6460            else gameMode = MachinePlaysBlack;                 
6461            DrawPosition(FALSE, boards[currentMove]);
6462         }
6463         return;
6464     }
6465
6466     /*
6467      * Look for communication commands
6468      */
6469     if (!strncmp(message, "telluser ", 9)) {
6470         DisplayNote(message + 9);
6471         return;
6472     }
6473     if (!strncmp(message, "tellusererror ", 14)) {
6474         cps->userError = 1;
6475         DisplayError(message + 14, 0);
6476         return;
6477     }
6478     if (!strncmp(message, "tellopponent ", 13)) {
6479       if (appData.icsActive) {
6480         if (loggedOn) {
6481           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6482           SendToICS(buf1);
6483         }
6484       } else {
6485         DisplayNote(message + 13);
6486       }
6487       return;
6488     }
6489     if (!strncmp(message, "tellothers ", 11)) {
6490       if (appData.icsActive) {
6491         if (loggedOn) {
6492           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6493           SendToICS(buf1);
6494         }
6495       }
6496       return;
6497     }
6498     if (!strncmp(message, "tellall ", 8)) {
6499       if (appData.icsActive) {
6500         if (loggedOn) {
6501           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6502           SendToICS(buf1);
6503         }
6504       } else {
6505         DisplayNote(message + 8);
6506       }
6507       return;
6508     }
6509     if (strncmp(message, "warning", 7) == 0) {
6510         /* Undocumented feature, use tellusererror in new code */
6511         DisplayError(message, 0);
6512         return;
6513     }
6514     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6515         strcpy(realname, cps->tidy);
6516         strcat(realname, " query");
6517         AskQuestion(realname, buf2, buf1, cps->pr);
6518         return;
6519     }
6520     /* Commands from the engine directly to ICS.  We don't allow these to be 
6521      *  sent until we are logged on. Crafty kibitzes have been known to 
6522      *  interfere with the login process.
6523      */
6524     if (loggedOn) {
6525         if (!strncmp(message, "tellics ", 8)) {
6526             SendToICS(message + 8);
6527             SendToICS("\n");
6528             return;
6529         }
6530         if (!strncmp(message, "tellicsnoalias ", 15)) {
6531             SendToICS(ics_prefix);
6532             SendToICS(message + 15);
6533             SendToICS("\n");
6534             return;
6535         }
6536         /* The following are for backward compatibility only */
6537         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6538             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6539             SendToICS(ics_prefix);
6540             SendToICS(message);
6541             SendToICS("\n");
6542             return;
6543         }
6544     }
6545     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6546         return;
6547     }
6548     /*
6549      * If the move is illegal, cancel it and redraw the board.
6550      * Also deal with other error cases.  Matching is rather loose
6551      * here to accommodate engines written before the spec.
6552      */
6553     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6554         strncmp(message, "Error", 5) == 0) {
6555         if (StrStr(message, "name") || 
6556             StrStr(message, "rating") || StrStr(message, "?") ||
6557             StrStr(message, "result") || StrStr(message, "board") ||
6558             StrStr(message, "bk") || StrStr(message, "computer") ||
6559             StrStr(message, "variant") || StrStr(message, "hint") ||
6560             StrStr(message, "random") || StrStr(message, "depth") ||
6561             StrStr(message, "accepted")) {
6562             return;
6563         }
6564         if (StrStr(message, "protover")) {
6565           /* Program is responding to input, so it's apparently done
6566              initializing, and this error message indicates it is
6567              protocol version 1.  So we don't need to wait any longer
6568              for it to initialize and send feature commands. */
6569           FeatureDone(cps, 1);
6570           cps->protocolVersion = 1;
6571           return;
6572         }
6573         cps->maybeThinking = FALSE;
6574
6575         if (StrStr(message, "draw")) {
6576             /* Program doesn't have "draw" command */
6577             cps->sendDrawOffers = 0;
6578             return;
6579         }
6580         if (cps->sendTime != 1 &&
6581             (StrStr(message, "time") || StrStr(message, "otim"))) {
6582           /* Program apparently doesn't have "time" or "otim" command */
6583           cps->sendTime = 0;
6584           return;
6585         }
6586         if (StrStr(message, "analyze")) {
6587             cps->analysisSupport = FALSE;
6588             cps->analyzing = FALSE;
6589             Reset(FALSE, TRUE);
6590             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6591             DisplayError(buf2, 0);
6592             return;
6593         }
6594         if (StrStr(message, "(no matching move)st")) {
6595           /* Special kludge for GNU Chess 4 only */
6596           cps->stKludge = TRUE;
6597           SendTimeControl(cps, movesPerSession, timeControl,
6598                           timeIncrement, appData.searchDepth,
6599                           searchTime);
6600           return;
6601         }
6602         if (StrStr(message, "(no matching move)sd")) {
6603           /* Special kludge for GNU Chess 4 only */
6604           cps->sdKludge = TRUE;
6605           SendTimeControl(cps, movesPerSession, timeControl,
6606                           timeIncrement, appData.searchDepth,
6607                           searchTime);
6608           return;
6609         }
6610         if (!StrStr(message, "llegal")) {
6611             return;
6612         }
6613         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6614             gameMode == IcsIdle) return;
6615         if (forwardMostMove <= backwardMostMove) return;
6616         if (pausing) PauseEvent();
6617       if(appData.forceIllegal) {
6618             // [HGM] illegal: machine refused move; force position after move into it
6619           SendToProgram("force\n", cps);
6620           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6621                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6622                 // when black is to move, while there might be nothing on a2 or black
6623                 // might already have the move. So send the board as if white has the move.
6624                 // But first we must change the stm of the engine, as it refused the last move
6625                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6626                 if(WhiteOnMove(forwardMostMove)) {
6627                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6628                     SendBoard(cps, forwardMostMove); // kludgeless board
6629                 } else {
6630                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6631                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6632                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6633                 }
6634           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6635             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6636                  gameMode == TwoMachinesPlay)
6637               SendToProgram("go\n", cps);
6638             return;
6639       } else
6640         if (gameMode == PlayFromGameFile) {
6641             /* Stop reading this game file */
6642             gameMode = EditGame;
6643             ModeHighlight();
6644         }
6645         currentMove = --forwardMostMove;
6646         DisplayMove(currentMove-1); /* before DisplayMoveError */
6647         SwitchClocks();
6648         DisplayBothClocks();
6649         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6650                 parseList[currentMove], cps->which);
6651         DisplayMoveError(buf1);
6652         DrawPosition(FALSE, boards[currentMove]);
6653
6654         /* [HGM] illegal-move claim should forfeit game when Xboard */
6655         /* only passes fully legal moves                            */
6656         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6657             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6658                                 "False illegal-move claim", GE_XBOARD );
6659         }
6660         return;
6661     }
6662     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6663         /* Program has a broken "time" command that
6664            outputs a string not ending in newline.
6665            Don't use it. */
6666         cps->sendTime = 0;
6667     }
6668     
6669     /*
6670      * If chess program startup fails, exit with an error message.
6671      * Attempts to recover here are futile.
6672      */
6673     if ((StrStr(message, "unknown host") != NULL)
6674         || (StrStr(message, "No remote directory") != NULL)
6675         || (StrStr(message, "not found") != NULL)
6676         || (StrStr(message, "No such file") != NULL)
6677         || (StrStr(message, "can't alloc") != NULL)
6678         || (StrStr(message, "Permission denied") != NULL)) {
6679
6680         cps->maybeThinking = FALSE;
6681         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6682                 cps->which, cps->program, cps->host, message);
6683         RemoveInputSource(cps->isr);
6684         DisplayFatalError(buf1, 0, 1);
6685         return;
6686     }
6687     
6688     /* 
6689      * Look for hint output
6690      */
6691     if (sscanf(message, "Hint: %s", buf1) == 1) {
6692         if (cps == &first && hintRequested) {
6693             hintRequested = FALSE;
6694             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6695                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6696                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6697                                     PosFlags(forwardMostMove),
6698                                     fromY, fromX, toY, toX, promoChar, buf1);
6699                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6700                 DisplayInformation(buf2);
6701             } else {
6702                 /* Hint move could not be parsed!? */
6703               snprintf(buf2, sizeof(buf2),
6704                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6705                         buf1, cps->which);
6706                 DisplayError(buf2, 0);
6707             }
6708         } else {
6709             strcpy(lastHint, buf1);
6710         }
6711         return;
6712     }
6713
6714     /*
6715      * Ignore other messages if game is not in progress
6716      */
6717     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6718         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6719
6720     /*
6721      * look for win, lose, draw, or draw offer
6722      */
6723     if (strncmp(message, "1-0", 3) == 0) {
6724         char *p, *q, *r = "";
6725         p = strchr(message, '{');
6726         if (p) {
6727             q = strchr(p, '}');
6728             if (q) {
6729                 *q = NULLCHAR;
6730                 r = p + 1;
6731             }
6732         }
6733         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6734         return;
6735     } else if (strncmp(message, "0-1", 3) == 0) {
6736         char *p, *q, *r = "";
6737         p = strchr(message, '{');
6738         if (p) {
6739             q = strchr(p, '}');
6740             if (q) {
6741                 *q = NULLCHAR;
6742                 r = p + 1;
6743             }
6744         }
6745         /* Kludge for Arasan 4.1 bug */
6746         if (strcmp(r, "Black resigns") == 0) {
6747             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6748             return;
6749         }
6750         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6751         return;
6752     } else if (strncmp(message, "1/2", 3) == 0) {
6753         char *p, *q, *r = "";
6754         p = strchr(message, '{');
6755         if (p) {
6756             q = strchr(p, '}');
6757             if (q) {
6758                 *q = NULLCHAR;
6759                 r = p + 1;
6760             }
6761         }
6762             
6763         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6764         return;
6765
6766     } else if (strncmp(message, "White resign", 12) == 0) {
6767         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6768         return;
6769     } else if (strncmp(message, "Black resign", 12) == 0) {
6770         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6771         return;
6772     } else if (strncmp(message, "White matches", 13) == 0 ||
6773                strncmp(message, "Black matches", 13) == 0   ) {
6774         /* [HGM] ignore GNUShogi noises */
6775         return;
6776     } else if (strncmp(message, "White", 5) == 0 &&
6777                message[5] != '(' &&
6778                StrStr(message, "Black") == NULL) {
6779         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6780         return;
6781     } else if (strncmp(message, "Black", 5) == 0 &&
6782                message[5] != '(') {
6783         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6784         return;
6785     } else if (strcmp(message, "resign") == 0 ||
6786                strcmp(message, "computer resigns") == 0) {
6787         switch (gameMode) {
6788           case MachinePlaysBlack:
6789           case IcsPlayingBlack:
6790             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6791             break;
6792           case MachinePlaysWhite:
6793           case IcsPlayingWhite:
6794             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6795             break;
6796           case TwoMachinesPlay:
6797             if (cps->twoMachinesColor[0] == 'w')
6798               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6799             else
6800               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6801             break;
6802           default:
6803             /* can't happen */
6804             break;
6805         }
6806         return;
6807     } else if (strncmp(message, "opponent mates", 14) == 0) {
6808         switch (gameMode) {
6809           case MachinePlaysBlack:
6810           case IcsPlayingBlack:
6811             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6812             break;
6813           case MachinePlaysWhite:
6814           case IcsPlayingWhite:
6815             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6816             break;
6817           case TwoMachinesPlay:
6818             if (cps->twoMachinesColor[0] == 'w')
6819               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6820             else
6821               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6822             break;
6823           default:
6824             /* can't happen */
6825             break;
6826         }
6827         return;
6828     } else if (strncmp(message, "computer mates", 14) == 0) {
6829         switch (gameMode) {
6830           case MachinePlaysBlack:
6831           case IcsPlayingBlack:
6832             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6833             break;
6834           case MachinePlaysWhite:
6835           case IcsPlayingWhite:
6836             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6837             break;
6838           case TwoMachinesPlay:
6839             if (cps->twoMachinesColor[0] == 'w')
6840               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6841             else
6842               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6843             break;
6844           default:
6845             /* can't happen */
6846             break;
6847         }
6848         return;
6849     } else if (strncmp(message, "checkmate", 9) == 0) {
6850         if (WhiteOnMove(forwardMostMove)) {
6851             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6852         } else {
6853             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6854         }
6855         return;
6856     } else if (strstr(message, "Draw") != NULL ||
6857                strstr(message, "game is a draw") != NULL) {
6858         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6859         return;
6860     } else if (strstr(message, "offer") != NULL &&
6861                strstr(message, "draw") != NULL) {
6862 #if ZIPPY
6863         if (appData.zippyPlay && first.initDone) {
6864             /* Relay offer to ICS */
6865             SendToICS(ics_prefix);
6866             SendToICS("draw\n");
6867         }
6868 #endif
6869         cps->offeredDraw = 2; /* valid until this engine moves twice */
6870         if (gameMode == TwoMachinesPlay) {
6871             if (cps->other->offeredDraw) {
6872                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6873             /* [HGM] in two-machine mode we delay relaying draw offer      */
6874             /* until after we also have move, to see if it is really claim */
6875             }
6876         } else if (gameMode == MachinePlaysWhite ||
6877                    gameMode == MachinePlaysBlack) {
6878           if (userOfferedDraw) {
6879             DisplayInformation(_("Machine accepts your draw offer"));
6880             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6881           } else {
6882             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6883           }
6884         }
6885     }
6886
6887     
6888     /*
6889      * Look for thinking output
6890      */
6891     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6892           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6893                                 ) {
6894         int plylev, mvleft, mvtot, curscore, time;
6895         char mvname[MOVE_LEN];
6896         u64 nodes; // [DM]
6897         char plyext;
6898         int ignore = FALSE;
6899         int prefixHint = FALSE;
6900         mvname[0] = NULLCHAR;
6901
6902         switch (gameMode) {
6903           case MachinePlaysBlack:
6904           case IcsPlayingBlack:
6905             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6906             break;
6907           case MachinePlaysWhite:
6908           case IcsPlayingWhite:
6909             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6910             break;
6911           case AnalyzeMode:
6912           case AnalyzeFile:
6913             break;
6914           case IcsObserving: /* [DM] icsEngineAnalyze */
6915             if (!appData.icsEngineAnalyze) ignore = TRUE;
6916             break;
6917           case TwoMachinesPlay:
6918             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6919                 ignore = TRUE;
6920             }
6921             break;
6922           default:
6923             ignore = TRUE;
6924             break;
6925         }
6926
6927         if (!ignore) {
6928             buf1[0] = NULLCHAR;
6929             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6930                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6931
6932                 if (plyext != ' ' && plyext != '\t') {
6933                     time *= 100;
6934                 }
6935
6936                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6937                 if( cps->scoreIsAbsolute && 
6938                     ( gameMode == MachinePlaysBlack ||
6939                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6940                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6941                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6942                      !WhiteOnMove(currentMove)
6943                     ) )
6944                 {
6945                     curscore = -curscore;
6946                 }
6947
6948
6949                 programStats.depth = plylev;
6950                 programStats.nodes = nodes;
6951                 programStats.time = time;
6952                 programStats.score = curscore;
6953                 programStats.got_only_move = 0;
6954
6955                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6956                         int ticklen;
6957
6958                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6959                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6960                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6961                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6962                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6963                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6964                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6965                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6966                 }
6967
6968                 /* Buffer overflow protection */
6969                 if (buf1[0] != NULLCHAR) {
6970                     if (strlen(buf1) >= sizeof(programStats.movelist)
6971                         && appData.debugMode) {
6972                         fprintf(debugFP,
6973                                 "PV is too long; using the first %u bytes.\n",
6974                                 (unsigned) sizeof(programStats.movelist) - 1);
6975                     }
6976
6977                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6978                 } else {
6979                     sprintf(programStats.movelist, " no PV\n");
6980                 }
6981
6982                 if (programStats.seen_stat) {
6983                     programStats.ok_to_send = 1;
6984                 }
6985
6986                 if (strchr(programStats.movelist, '(') != NULL) {
6987                     programStats.line_is_book = 1;
6988                     programStats.nr_moves = 0;
6989                     programStats.moves_left = 0;
6990                 } else {
6991                     programStats.line_is_book = 0;
6992                 }
6993
6994                 SendProgramStatsToFrontend( cps, &programStats );
6995
6996                 /* 
6997                     [AS] Protect the thinkOutput buffer from overflow... this
6998                     is only useful if buf1 hasn't overflowed first!
6999                 */
7000                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7001                         plylev, 
7002                         (gameMode == TwoMachinesPlay ?
7003                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7004                         ((double) curscore) / 100.0,
7005                         prefixHint ? lastHint : "",
7006                         prefixHint ? " " : "" );
7007
7008                 if( buf1[0] != NULLCHAR ) {
7009                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7010
7011                     if( strlen(buf1) > max_len ) {
7012                         if( appData.debugMode) {
7013                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7014                         }
7015                         buf1[max_len+1] = '\0';
7016                     }
7017
7018                     strcat( thinkOutput, buf1 );
7019                 }
7020
7021                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7022                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7023                     DisplayMove(currentMove - 1);
7024                 }
7025                 return;
7026
7027             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7028                 /* crafty (9.25+) says "(only move) <move>"
7029                  * if there is only 1 legal move
7030                  */
7031                 sscanf(p, "(only move) %s", buf1);
7032                 sprintf(thinkOutput, "%s (only move)", buf1);
7033                 sprintf(programStats.movelist, "%s (only move)", buf1);
7034                 programStats.depth = 1;
7035                 programStats.nr_moves = 1;
7036                 programStats.moves_left = 1;
7037                 programStats.nodes = 1;
7038                 programStats.time = 1;
7039                 programStats.got_only_move = 1;
7040
7041                 /* Not really, but we also use this member to
7042                    mean "line isn't going to change" (Crafty
7043                    isn't searching, so stats won't change) */
7044                 programStats.line_is_book = 1;
7045
7046                 SendProgramStatsToFrontend( cps, &programStats );
7047                 
7048                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7049                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7050                     DisplayMove(currentMove - 1);
7051                 }
7052                 return;
7053             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7054                               &time, &nodes, &plylev, &mvleft,
7055                               &mvtot, mvname) >= 5) {
7056                 /* The stat01: line is from Crafty (9.29+) in response
7057                    to the "." command */
7058                 programStats.seen_stat = 1;
7059                 cps->maybeThinking = TRUE;
7060
7061                 if (programStats.got_only_move || !appData.periodicUpdates)
7062                   return;
7063
7064                 programStats.depth = plylev;
7065                 programStats.time = time;
7066                 programStats.nodes = nodes;
7067                 programStats.moves_left = mvleft;
7068                 programStats.nr_moves = mvtot;
7069                 strcpy(programStats.move_name, mvname);
7070                 programStats.ok_to_send = 1;
7071                 programStats.movelist[0] = '\0';
7072
7073                 SendProgramStatsToFrontend( cps, &programStats );
7074
7075                 return;
7076
7077             } else if (strncmp(message,"++",2) == 0) {
7078                 /* Crafty 9.29+ outputs this */
7079                 programStats.got_fail = 2;
7080                 return;
7081
7082             } else if (strncmp(message,"--",2) == 0) {
7083                 /* Crafty 9.29+ outputs this */
7084                 programStats.got_fail = 1;
7085                 return;
7086
7087             } else if (thinkOutput[0] != NULLCHAR &&
7088                        strncmp(message, "    ", 4) == 0) {
7089                 unsigned message_len;
7090
7091                 p = message;
7092                 while (*p && *p == ' ') p++;
7093
7094                 message_len = strlen( p );
7095
7096                 /* [AS] Avoid buffer overflow */
7097                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7098                     strcat(thinkOutput, " ");
7099                     strcat(thinkOutput, p);
7100                 }
7101
7102                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7103                     strcat(programStats.movelist, " ");
7104                     strcat(programStats.movelist, p);
7105                 }
7106
7107                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7108                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7109                     DisplayMove(currentMove - 1);
7110                 }
7111                 return;
7112             }
7113         }
7114         else {
7115             buf1[0] = NULLCHAR;
7116
7117             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7118                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7119             {
7120                 ChessProgramStats cpstats;
7121
7122                 if (plyext != ' ' && plyext != '\t') {
7123                     time *= 100;
7124                 }
7125
7126                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7127                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7128                     curscore = -curscore;
7129                 }
7130
7131                 cpstats.depth = plylev;
7132                 cpstats.nodes = nodes;
7133                 cpstats.time = time;
7134                 cpstats.score = curscore;
7135                 cpstats.got_only_move = 0;
7136                 cpstats.movelist[0] = '\0';
7137
7138                 if (buf1[0] != NULLCHAR) {
7139                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7140                 }
7141
7142                 cpstats.ok_to_send = 0;
7143                 cpstats.line_is_book = 0;
7144                 cpstats.nr_moves = 0;
7145                 cpstats.moves_left = 0;
7146
7147                 SendProgramStatsToFrontend( cps, &cpstats );
7148             }
7149         }
7150     }
7151 }
7152
7153
7154 /* Parse a game score from the character string "game", and
7155    record it as the history of the current game.  The game
7156    score is NOT assumed to start from the standard position. 
7157    The display is not updated in any way.
7158    */
7159 void
7160 ParseGameHistory(game)
7161      char *game;
7162 {
7163     ChessMove moveType;
7164     int fromX, fromY, toX, toY, boardIndex;
7165     char promoChar;
7166     char *p, *q;
7167     char buf[MSG_SIZ];
7168
7169     if (appData.debugMode)
7170       fprintf(debugFP, "Parsing game history: %s\n", game);
7171
7172     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7173     gameInfo.site = StrSave(appData.icsHost);
7174     gameInfo.date = PGNDate();
7175     gameInfo.round = StrSave("-");
7176
7177     /* Parse out names of players */
7178     while (*game == ' ') game++;
7179     p = buf;
7180     while (*game != ' ') *p++ = *game++;
7181     *p = NULLCHAR;
7182     gameInfo.white = StrSave(buf);
7183     while (*game == ' ') game++;
7184     p = buf;
7185     while (*game != ' ' && *game != '\n') *p++ = *game++;
7186     *p = NULLCHAR;
7187     gameInfo.black = StrSave(buf);
7188
7189     /* Parse moves */
7190     boardIndex = blackPlaysFirst ? 1 : 0;
7191     yynewstr(game);
7192     for (;;) {
7193         yyboardindex = boardIndex;
7194         moveType = (ChessMove) yylex();
7195         switch (moveType) {
7196           case IllegalMove:             /* maybe suicide chess, etc. */
7197   if (appData.debugMode) {
7198     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7199     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7200     setbuf(debugFP, NULL);
7201   }
7202           case WhitePromotionChancellor:
7203           case BlackPromotionChancellor:
7204           case WhitePromotionArchbishop:
7205           case BlackPromotionArchbishop:
7206           case WhitePromotionQueen:
7207           case BlackPromotionQueen:
7208           case WhitePromotionRook:
7209           case BlackPromotionRook:
7210           case WhitePromotionBishop:
7211           case BlackPromotionBishop:
7212           case WhitePromotionKnight:
7213           case BlackPromotionKnight:
7214           case WhitePromotionKing:
7215           case BlackPromotionKing:
7216           case NormalMove:
7217           case WhiteCapturesEnPassant:
7218           case BlackCapturesEnPassant:
7219           case WhiteKingSideCastle:
7220           case WhiteQueenSideCastle:
7221           case BlackKingSideCastle:
7222           case BlackQueenSideCastle:
7223           case WhiteKingSideCastleWild:
7224           case WhiteQueenSideCastleWild:
7225           case BlackKingSideCastleWild:
7226           case BlackQueenSideCastleWild:
7227           /* PUSH Fabien */
7228           case WhiteHSideCastleFR:
7229           case WhiteASideCastleFR:
7230           case BlackHSideCastleFR:
7231           case BlackASideCastleFR:
7232           /* POP Fabien */
7233             fromX = currentMoveString[0] - AAA;
7234             fromY = currentMoveString[1] - ONE;
7235             toX = currentMoveString[2] - AAA;
7236             toY = currentMoveString[3] - ONE;
7237             promoChar = currentMoveString[4];
7238             break;
7239           case WhiteDrop:
7240           case BlackDrop:
7241             fromX = moveType == WhiteDrop ?
7242               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7243             (int) CharToPiece(ToLower(currentMoveString[0]));
7244             fromY = DROP_RANK;
7245             toX = currentMoveString[2] - AAA;
7246             toY = currentMoveString[3] - ONE;
7247             promoChar = NULLCHAR;
7248             break;
7249           case AmbiguousMove:
7250             /* bug? */
7251             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7252   if (appData.debugMode) {
7253     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7254     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7255     setbuf(debugFP, NULL);
7256   }
7257             DisplayError(buf, 0);
7258             return;
7259           case ImpossibleMove:
7260             /* bug? */
7261             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7262   if (appData.debugMode) {
7263     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7264     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7265     setbuf(debugFP, NULL);
7266   }
7267             DisplayError(buf, 0);
7268             return;
7269           case (ChessMove) 0:   /* end of file */
7270             if (boardIndex < backwardMostMove) {
7271                 /* Oops, gap.  How did that happen? */
7272                 DisplayError(_("Gap in move list"), 0);
7273                 return;
7274             }
7275             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7276             if (boardIndex > forwardMostMove) {
7277                 forwardMostMove = boardIndex;
7278             }
7279             return;
7280           case ElapsedTime:
7281             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7282                 strcat(parseList[boardIndex-1], " ");
7283                 strcat(parseList[boardIndex-1], yy_text);
7284             }
7285             continue;
7286           case Comment:
7287           case PGNTag:
7288           case NAG:
7289           default:
7290             /* ignore */
7291             continue;
7292           case WhiteWins:
7293           case BlackWins:
7294           case GameIsDrawn:
7295           case GameUnfinished:
7296             if (gameMode == IcsExamining) {
7297                 if (boardIndex < backwardMostMove) {
7298                     /* Oops, gap.  How did that happen? */
7299                     return;
7300                 }
7301                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7302                 return;
7303             }
7304             gameInfo.result = moveType;
7305             p = strchr(yy_text, '{');
7306             if (p == NULL) p = strchr(yy_text, '(');
7307             if (p == NULL) {
7308                 p = yy_text;
7309                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7310             } else {
7311                 q = strchr(p, *p == '{' ? '}' : ')');
7312                 if (q != NULL) *q = NULLCHAR;
7313                 p++;
7314             }
7315             gameInfo.resultDetails = StrSave(p);
7316             continue;
7317         }
7318         if (boardIndex >= forwardMostMove &&
7319             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7320             backwardMostMove = blackPlaysFirst ? 1 : 0;
7321             return;
7322         }
7323         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7324                                  fromY, fromX, toY, toX, promoChar,
7325                                  parseList[boardIndex]);
7326         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7327         /* currentMoveString is set as a side-effect of yylex */
7328         strcpy(moveList[boardIndex], currentMoveString);
7329         strcat(moveList[boardIndex], "\n");
7330         boardIndex++;
7331         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7332         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7333           case MT_NONE:
7334           case MT_STALEMATE:
7335           default:
7336             break;
7337           case MT_CHECK:
7338             if(gameInfo.variant != VariantShogi)
7339                 strcat(parseList[boardIndex - 1], "+");
7340             break;
7341           case MT_CHECKMATE:
7342           case MT_STAINMATE:
7343             strcat(parseList[boardIndex - 1], "#");
7344             break;
7345         }
7346     }
7347 }
7348
7349
7350 /* Apply a move to the given board  */
7351 void
7352 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7353      int fromX, fromY, toX, toY;
7354      int promoChar;
7355      Board board;
7356 {
7357   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7358
7359     /* [HGM] compute & store e.p. status and castling rights for new position */
7360     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7361     { int i;
7362
7363       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7364       oldEP = (signed char)board[EP_STATUS];
7365       board[EP_STATUS] = EP_NONE;
7366
7367       if( board[toY][toX] != EmptySquare ) 
7368            board[EP_STATUS] = EP_CAPTURE;  
7369
7370       if( board[fromY][fromX] == WhitePawn ) {
7371            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7372                board[EP_STATUS] = EP_PAWN_MOVE;
7373            if( toY-fromY==2) {
7374                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7375                         gameInfo.variant != VariantBerolina || toX < fromX)
7376                       board[EP_STATUS] = toX | berolina;
7377                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7378                         gameInfo.variant != VariantBerolina || toX > fromX) 
7379                       board[EP_STATUS] = toX;
7380            }
7381       } else 
7382       if( board[fromY][fromX] == BlackPawn ) {
7383            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7384                board[EP_STATUS] = EP_PAWN_MOVE; 
7385            if( toY-fromY== -2) {
7386                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7387                         gameInfo.variant != VariantBerolina || toX < fromX)
7388                       board[EP_STATUS] = toX | berolina;
7389                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7390                         gameInfo.variant != VariantBerolina || toX > fromX) 
7391                       board[EP_STATUS] = toX;
7392            }
7393        }
7394
7395        for(i=0; i<nrCastlingRights; i++) {
7396            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7397               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7398              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7399        }
7400
7401     }
7402
7403   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7404   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7405        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7406          
7407   if (fromX == toX && fromY == toY) return;
7408
7409   if (fromY == DROP_RANK) {
7410         /* must be first */
7411         piece = board[toY][toX] = (ChessSquare) fromX;
7412   } else {
7413      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7414      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7415      if(gameInfo.variant == VariantKnightmate)
7416          king += (int) WhiteUnicorn - (int) WhiteKing;
7417
7418     /* Code added by Tord: */
7419     /* FRC castling assumed when king captures friendly rook. */
7420     if (board[fromY][fromX] == WhiteKing &&
7421              board[toY][toX] == WhiteRook) {
7422       board[fromY][fromX] = EmptySquare;
7423       board[toY][toX] = EmptySquare;
7424       if(toX > fromX) {
7425         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7426       } else {
7427         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7428       }
7429     } else if (board[fromY][fromX] == BlackKing &&
7430                board[toY][toX] == BlackRook) {
7431       board[fromY][fromX] = EmptySquare;
7432       board[toY][toX] = EmptySquare;
7433       if(toX > fromX) {
7434         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7435       } else {
7436         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7437       }
7438     /* End of code added by Tord */
7439
7440     } else if (board[fromY][fromX] == king
7441         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7442         && toY == fromY && toX > fromX+1) {
7443         board[fromY][fromX] = EmptySquare;
7444         board[toY][toX] = king;
7445         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7446         board[fromY][BOARD_RGHT-1] = EmptySquare;
7447     } else if (board[fromY][fromX] == king
7448         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7449                && toY == fromY && toX < fromX-1) {
7450         board[fromY][fromX] = EmptySquare;
7451         board[toY][toX] = king;
7452         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7453         board[fromY][BOARD_LEFT] = EmptySquare;
7454     } else if (board[fromY][fromX] == WhitePawn
7455                && toY == BOARD_HEIGHT-1
7456                && gameInfo.variant != VariantXiangqi
7457                ) {
7458         /* white pawn promotion */
7459         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7460         if (board[toY][toX] == EmptySquare) {
7461             board[toY][toX] = WhiteQueen;
7462         }
7463         if(gameInfo.variant==VariantBughouse ||
7464            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7465             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7466         board[fromY][fromX] = EmptySquare;
7467     } else if ((fromY == BOARD_HEIGHT-4)
7468                && (toX != fromX)
7469                && gameInfo.variant != VariantXiangqi
7470                && gameInfo.variant != VariantBerolina
7471                && (board[fromY][fromX] == WhitePawn)
7472                && (board[toY][toX] == EmptySquare)) {
7473         board[fromY][fromX] = EmptySquare;
7474         board[toY][toX] = WhitePawn;
7475         captured = board[toY - 1][toX];
7476         board[toY - 1][toX] = EmptySquare;
7477     } else if ((fromY == BOARD_HEIGHT-4)
7478                && (toX == fromX)
7479                && gameInfo.variant == VariantBerolina
7480                && (board[fromY][fromX] == WhitePawn)
7481                && (board[toY][toX] == EmptySquare)) {
7482         board[fromY][fromX] = EmptySquare;
7483         board[toY][toX] = WhitePawn;
7484         if(oldEP & EP_BEROLIN_A) {
7485                 captured = board[fromY][fromX-1];
7486                 board[fromY][fromX-1] = EmptySquare;
7487         }else{  captured = board[fromY][fromX+1];
7488                 board[fromY][fromX+1] = EmptySquare;
7489         }
7490     } else if (board[fromY][fromX] == king
7491         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7492                && toY == fromY && toX > fromX+1) {
7493         board[fromY][fromX] = EmptySquare;
7494         board[toY][toX] = king;
7495         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7496         board[fromY][BOARD_RGHT-1] = EmptySquare;
7497     } else if (board[fromY][fromX] == king
7498         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7499                && toY == fromY && toX < fromX-1) {
7500         board[fromY][fromX] = EmptySquare;
7501         board[toY][toX] = king;
7502         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7503         board[fromY][BOARD_LEFT] = EmptySquare;
7504     } else if (fromY == 7 && fromX == 3
7505                && board[fromY][fromX] == BlackKing
7506                && toY == 7 && toX == 5) {
7507         board[fromY][fromX] = EmptySquare;
7508         board[toY][toX] = BlackKing;
7509         board[fromY][7] = EmptySquare;
7510         board[toY][4] = BlackRook;
7511     } else if (fromY == 7 && fromX == 3
7512                && board[fromY][fromX] == BlackKing
7513                && toY == 7 && toX == 1) {
7514         board[fromY][fromX] = EmptySquare;
7515         board[toY][toX] = BlackKing;
7516         board[fromY][0] = EmptySquare;
7517         board[toY][2] = BlackRook;
7518     } else if (board[fromY][fromX] == BlackPawn
7519                && toY == 0
7520                && gameInfo.variant != VariantXiangqi
7521                ) {
7522         /* black pawn promotion */
7523         board[0][toX] = CharToPiece(ToLower(promoChar));
7524         if (board[0][toX] == EmptySquare) {
7525             board[0][toX] = BlackQueen;
7526         }
7527         if(gameInfo.variant==VariantBughouse ||
7528            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7529             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7530         board[fromY][fromX] = EmptySquare;
7531     } else if ((fromY == 3)
7532                && (toX != fromX)
7533                && gameInfo.variant != VariantXiangqi
7534                && gameInfo.variant != VariantBerolina
7535                && (board[fromY][fromX] == BlackPawn)
7536                && (board[toY][toX] == EmptySquare)) {
7537         board[fromY][fromX] = EmptySquare;
7538         board[toY][toX] = BlackPawn;
7539         captured = board[toY + 1][toX];
7540         board[toY + 1][toX] = EmptySquare;
7541     } else if ((fromY == 3)
7542                && (toX == fromX)
7543                && gameInfo.variant == VariantBerolina
7544                && (board[fromY][fromX] == BlackPawn)
7545                && (board[toY][toX] == EmptySquare)) {
7546         board[fromY][fromX] = EmptySquare;
7547         board[toY][toX] = BlackPawn;
7548         if(oldEP & EP_BEROLIN_A) {
7549                 captured = board[fromY][fromX-1];
7550                 board[fromY][fromX-1] = EmptySquare;
7551         }else{  captured = board[fromY][fromX+1];
7552                 board[fromY][fromX+1] = EmptySquare;
7553         }
7554     } else {
7555         board[toY][toX] = board[fromY][fromX];
7556         board[fromY][fromX] = EmptySquare;
7557     }
7558
7559     /* [HGM] now we promote for Shogi, if needed */
7560     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7561         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7562   }
7563
7564     if (gameInfo.holdingsWidth != 0) {
7565
7566       /* !!A lot more code needs to be written to support holdings  */
7567       /* [HGM] OK, so I have written it. Holdings are stored in the */
7568       /* penultimate board files, so they are automaticlly stored   */
7569       /* in the game history.                                       */
7570       if (fromY == DROP_RANK) {
7571         /* Delete from holdings, by decreasing count */
7572         /* and erasing image if necessary            */
7573         p = (int) fromX;
7574         if(p < (int) BlackPawn) { /* white drop */
7575              p -= (int)WhitePawn;
7576                  p = PieceToNumber((ChessSquare)p);
7577              if(p >= gameInfo.holdingsSize) p = 0;
7578              if(--board[p][BOARD_WIDTH-2] <= 0)
7579                   board[p][BOARD_WIDTH-1] = EmptySquare;
7580              if((int)board[p][BOARD_WIDTH-2] < 0)
7581                         board[p][BOARD_WIDTH-2] = 0;
7582         } else {                  /* black drop */
7583              p -= (int)BlackPawn;
7584                  p = PieceToNumber((ChessSquare)p);
7585              if(p >= gameInfo.holdingsSize) p = 0;
7586              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7587                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7588              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7589                         board[BOARD_HEIGHT-1-p][1] = 0;
7590         }
7591       }
7592       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7593           && gameInfo.variant != VariantBughouse        ) {
7594         /* [HGM] holdings: Add to holdings, if holdings exist */
7595         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7596                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7597                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7598         }
7599         p = (int) captured;
7600         if (p >= (int) BlackPawn) {
7601           p -= (int)BlackPawn;
7602           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7603                   /* in Shogi restore piece to its original  first */
7604                   captured = (ChessSquare) (DEMOTED captured);
7605                   p = DEMOTED p;
7606           }
7607           p = PieceToNumber((ChessSquare)p);
7608           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7609           board[p][BOARD_WIDTH-2]++;
7610           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7611         } else {
7612           p -= (int)WhitePawn;
7613           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7614                   captured = (ChessSquare) (DEMOTED captured);
7615                   p = DEMOTED p;
7616           }
7617           p = PieceToNumber((ChessSquare)p);
7618           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7619           board[BOARD_HEIGHT-1-p][1]++;
7620           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7621         }
7622       }
7623     } else if (gameInfo.variant == VariantAtomic) {
7624       if (captured != EmptySquare) {
7625         int y, x;
7626         for (y = toY-1; y <= toY+1; y++) {
7627           for (x = toX-1; x <= toX+1; x++) {
7628             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7629                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7630               board[y][x] = EmptySquare;
7631             }
7632           }
7633         }
7634         board[toY][toX] = EmptySquare;
7635       }
7636     }
7637     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7638         /* [HGM] Shogi promotions */
7639         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7640     }
7641
7642     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7643                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7644         // [HGM] superchess: take promotion piece out of holdings
7645         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7646         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7647             if(!--board[k][BOARD_WIDTH-2])
7648                 board[k][BOARD_WIDTH-1] = EmptySquare;
7649         } else {
7650             if(!--board[BOARD_HEIGHT-1-k][1])
7651                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7652         }
7653     }
7654
7655 }
7656
7657 /* Updates forwardMostMove */
7658 void
7659 MakeMove(fromX, fromY, toX, toY, promoChar)
7660      int fromX, fromY, toX, toY;
7661      int promoChar;
7662 {
7663 //    forwardMostMove++; // [HGM] bare: moved downstream
7664
7665     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7666         int timeLeft; static int lastLoadFlag=0; int king, piece;
7667         piece = boards[forwardMostMove][fromY][fromX];
7668         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7669         if(gameInfo.variant == VariantKnightmate)
7670             king += (int) WhiteUnicorn - (int) WhiteKing;
7671         if(forwardMostMove == 0) {
7672             if(blackPlaysFirst) 
7673                 fprintf(serverMoves, "%s;", second.tidy);
7674             fprintf(serverMoves, "%s;", first.tidy);
7675             if(!blackPlaysFirst) 
7676                 fprintf(serverMoves, "%s;", second.tidy);
7677         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7678         lastLoadFlag = loadFlag;
7679         // print base move
7680         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7681         // print castling suffix
7682         if( toY == fromY && piece == king ) {
7683             if(toX-fromX > 1)
7684                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7685             if(fromX-toX >1)
7686                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7687         }
7688         // e.p. suffix
7689         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7690              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7691              boards[forwardMostMove][toY][toX] == EmptySquare
7692              && fromX != toX )
7693                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7694         // promotion suffix
7695         if(promoChar != NULLCHAR)
7696                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7697         if(!loadFlag) {
7698             fprintf(serverMoves, "/%d/%d",
7699                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7700             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7701             else                      timeLeft = blackTimeRemaining/1000;
7702             fprintf(serverMoves, "/%d", timeLeft);
7703         }
7704         fflush(serverMoves);
7705     }
7706
7707     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7708       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7709                         0, 1);
7710       return;
7711     }
7712     if (commentList[forwardMostMove+1] != NULL) {
7713         free(commentList[forwardMostMove+1]);
7714         commentList[forwardMostMove+1] = NULL;
7715     }
7716     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7717     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7718     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7719     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7720     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7721     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7722     gameInfo.result = GameUnfinished;
7723     if (gameInfo.resultDetails != NULL) {
7724         free(gameInfo.resultDetails);
7725         gameInfo.resultDetails = NULL;
7726     }
7727     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7728                               moveList[forwardMostMove - 1]);
7729     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7730                              PosFlags(forwardMostMove - 1),
7731                              fromY, fromX, toY, toX, promoChar,
7732                              parseList[forwardMostMove - 1]);
7733     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7734       case MT_NONE:
7735       case MT_STALEMATE:
7736       default:
7737         break;
7738       case MT_CHECK:
7739         if(gameInfo.variant != VariantShogi)
7740             strcat(parseList[forwardMostMove - 1], "+");
7741         break;
7742       case MT_CHECKMATE:
7743       case MT_STAINMATE:
7744         strcat(parseList[forwardMostMove - 1], "#");
7745         break;
7746     }
7747     if (appData.debugMode) {
7748         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7749     }
7750
7751 }
7752
7753 /* Updates currentMove if not pausing */
7754 void
7755 ShowMove(fromX, fromY, toX, toY)
7756 {
7757     int instant = (gameMode == PlayFromGameFile) ?
7758         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7759     if(appData.noGUI) return;
7760     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7761         if (!instant) {
7762             if (forwardMostMove == currentMove + 1) {
7763                 AnimateMove(boards[forwardMostMove - 1],
7764                             fromX, fromY, toX, toY);
7765             }
7766             if (appData.highlightLastMove) {
7767                 SetHighlights(fromX, fromY, toX, toY);
7768             }
7769         }
7770         currentMove = forwardMostMove;
7771     }
7772
7773     if (instant) return;
7774
7775     DisplayMove(currentMove - 1);
7776     DrawPosition(FALSE, boards[currentMove]);
7777     DisplayBothClocks();
7778     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7779 }
7780
7781 void SendEgtPath(ChessProgramState *cps)
7782 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7783         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7784
7785         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7786
7787         while(*p) {
7788             char c, *q = name+1, *r, *s;
7789
7790             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7791             while(*p && *p != ',') *q++ = *p++;
7792             *q++ = ':'; *q = 0;
7793             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7794                 strcmp(name, ",nalimov:") == 0 ) {
7795                 // take nalimov path from the menu-changeable option first, if it is defined
7796                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7797                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7798             } else
7799             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7800                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7801                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7802                 s = r = StrStr(s, ":") + 1; // beginning of path info
7803                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7804                 c = *r; *r = 0;             // temporarily null-terminate path info
7805                     *--q = 0;               // strip of trailig ':' from name
7806                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7807                 *r = c;
7808                 SendToProgram(buf,cps);     // send egtbpath command for this format
7809             }
7810             if(*p == ',') p++; // read away comma to position for next format name
7811         }
7812 }
7813
7814 void
7815 InitChessProgram(cps, setup)
7816      ChessProgramState *cps;
7817      int setup; /* [HGM] needed to setup FRC opening position */
7818 {
7819     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7820     if (appData.noChessProgram) return;
7821     hintRequested = FALSE;
7822     bookRequested = FALSE;
7823
7824     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7825     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7826     if(cps->memSize) { /* [HGM] memory */
7827         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7828         SendToProgram(buf, cps);
7829     }
7830     SendEgtPath(cps); /* [HGM] EGT */
7831     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7832         sprintf(buf, "cores %d\n", appData.smpCores);
7833         SendToProgram(buf, cps);
7834     }
7835
7836     SendToProgram(cps->initString, cps);
7837     if (gameInfo.variant != VariantNormal &&
7838         gameInfo.variant != VariantLoadable
7839         /* [HGM] also send variant if board size non-standard */
7840         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7841                                             ) {
7842       char *v = VariantName(gameInfo.variant);
7843       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7844         /* [HGM] in protocol 1 we have to assume all variants valid */
7845         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7846         DisplayFatalError(buf, 0, 1);
7847         return;
7848       }
7849
7850       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7851       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7852       if( gameInfo.variant == VariantXiangqi )
7853            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7854       if( gameInfo.variant == VariantShogi )
7855            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7856       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7857            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7858       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7859                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7860            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7861       if( gameInfo.variant == VariantCourier )
7862            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863       if( gameInfo.variant == VariantSuper )
7864            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7865       if( gameInfo.variant == VariantGreat )
7866            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7867
7868       if(overruled) {
7869            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7870                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7871            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7872            if(StrStr(cps->variants, b) == NULL) { 
7873                // specific sized variant not known, check if general sizing allowed
7874                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7875                    if(StrStr(cps->variants, "boardsize") == NULL) {
7876                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7877                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7878                        DisplayFatalError(buf, 0, 1);
7879                        return;
7880                    }
7881                    /* [HGM] here we really should compare with the maximum supported board size */
7882                }
7883            }
7884       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7885       sprintf(buf, "variant %s\n", b);
7886       SendToProgram(buf, cps);
7887     }
7888     currentlyInitializedVariant = gameInfo.variant;
7889
7890     /* [HGM] send opening position in FRC to first engine */
7891     if(setup) {
7892           SendToProgram("force\n", cps);
7893           SendBoard(cps, 0);
7894           /* engine is now in force mode! Set flag to wake it up after first move. */
7895           setboardSpoiledMachineBlack = 1;
7896     }
7897
7898     if (cps->sendICS) {
7899       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7900       SendToProgram(buf, cps);
7901     }
7902     cps->maybeThinking = FALSE;
7903     cps->offeredDraw = 0;
7904     if (!appData.icsActive) {
7905         SendTimeControl(cps, movesPerSession, timeControl,
7906                         timeIncrement, appData.searchDepth,
7907                         searchTime);
7908     }
7909     if (appData.showThinking 
7910         // [HGM] thinking: four options require thinking output to be sent
7911         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7912                                 ) {
7913         SendToProgram("post\n", cps);
7914     }
7915     SendToProgram("hard\n", cps);
7916     if (!appData.ponderNextMove) {
7917         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7918            it without being sure what state we are in first.  "hard"
7919            is not a toggle, so that one is OK.
7920          */
7921         SendToProgram("easy\n", cps);
7922     }
7923     if (cps->usePing) {
7924       sprintf(buf, "ping %d\n", ++cps->lastPing);
7925       SendToProgram(buf, cps);
7926     }
7927     cps->initDone = TRUE;
7928 }   
7929
7930
7931 void
7932 StartChessProgram(cps)
7933      ChessProgramState *cps;
7934 {
7935     char buf[MSG_SIZ];
7936     int err;
7937
7938     if (appData.noChessProgram) return;
7939     cps->initDone = FALSE;
7940
7941     if (strcmp(cps->host, "localhost") == 0) {
7942         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7943     } else if (*appData.remoteShell == NULLCHAR) {
7944         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7945     } else {
7946         if (*appData.remoteUser == NULLCHAR) {
7947           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7948                     cps->program);
7949         } else {
7950           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7951                     cps->host, appData.remoteUser, cps->program);
7952         }
7953         err = StartChildProcess(buf, "", &cps->pr);
7954     }
7955     
7956     if (err != 0) {
7957         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7958         DisplayFatalError(buf, err, 1);
7959         cps->pr = NoProc;
7960         cps->isr = NULL;
7961         return;
7962     }
7963     
7964     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7965     if (cps->protocolVersion > 1) {
7966       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7967       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7968       cps->comboCnt = 0;  //                and values of combo boxes
7969       SendToProgram(buf, cps);
7970     } else {
7971       SendToProgram("xboard\n", cps);
7972     }
7973 }
7974
7975
7976 void
7977 TwoMachinesEventIfReady P((void))
7978 {
7979   if (first.lastPing != first.lastPong) {
7980     DisplayMessage("", _("Waiting for first chess program"));
7981     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7982     return;
7983   }
7984   if (second.lastPing != second.lastPong) {
7985     DisplayMessage("", _("Waiting for second chess program"));
7986     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7987     return;
7988   }
7989   ThawUI();
7990   TwoMachinesEvent();
7991 }
7992
7993 void
7994 NextMatchGame P((void))
7995 {
7996     int index; /* [HGM] autoinc: step load index during match */
7997     Reset(FALSE, TRUE);
7998     if (*appData.loadGameFile != NULLCHAR) {
7999         index = appData.loadGameIndex;
8000         if(index < 0) { // [HGM] autoinc
8001             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8002             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8003         } 
8004         LoadGameFromFile(appData.loadGameFile,
8005                          index,
8006                          appData.loadGameFile, FALSE);
8007     } else if (*appData.loadPositionFile != NULLCHAR) {
8008         index = appData.loadPositionIndex;
8009         if(index < 0) { // [HGM] autoinc
8010             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8011             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8012         } 
8013         LoadPositionFromFile(appData.loadPositionFile,
8014                              index,
8015                              appData.loadPositionFile);
8016     }
8017     TwoMachinesEventIfReady();
8018 }
8019
8020 void UserAdjudicationEvent( int result )
8021 {
8022     ChessMove gameResult = GameIsDrawn;
8023
8024     if( result > 0 ) {
8025         gameResult = WhiteWins;
8026     }
8027     else if( result < 0 ) {
8028         gameResult = BlackWins;
8029     }
8030
8031     if( gameMode == TwoMachinesPlay ) {
8032         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8033     }
8034 }
8035
8036
8037 // [HGM] save: calculate checksum of game to make games easily identifiable
8038 int StringCheckSum(char *s)
8039 {
8040         int i = 0;
8041         if(s==NULL) return 0;
8042         while(*s) i = i*259 + *s++;
8043         return i;
8044 }
8045
8046 int GameCheckSum()
8047 {
8048         int i, sum=0;
8049         for(i=backwardMostMove; i<forwardMostMove; i++) {
8050                 sum += pvInfoList[i].depth;
8051                 sum += StringCheckSum(parseList[i]);
8052                 sum += StringCheckSum(commentList[i]);
8053                 sum *= 261;
8054         }
8055         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8056         return sum + StringCheckSum(commentList[i]);
8057 } // end of save patch
8058
8059 void
8060 GameEnds(result, resultDetails, whosays)
8061      ChessMove result;
8062      char *resultDetails;
8063      int whosays;
8064 {
8065     GameMode nextGameMode;
8066     int isIcsGame;
8067     char buf[MSG_SIZ];
8068
8069     if(endingGame) return; /* [HGM] crash: forbid recursion */
8070     endingGame = 1;
8071
8072     if (appData.debugMode) {
8073       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8074               result, resultDetails ? resultDetails : "(null)", whosays);
8075     }
8076
8077     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8078         /* If we are playing on ICS, the server decides when the
8079            game is over, but the engine can offer to draw, claim 
8080            a draw, or resign. 
8081          */
8082 #if ZIPPY
8083         if (appData.zippyPlay && first.initDone) {
8084             if (result == GameIsDrawn) {
8085                 /* In case draw still needs to be claimed */
8086                 SendToICS(ics_prefix);
8087                 SendToICS("draw\n");
8088             } else if (StrCaseStr(resultDetails, "resign")) {
8089                 SendToICS(ics_prefix);
8090                 SendToICS("resign\n");
8091             }
8092         }
8093 #endif
8094         endingGame = 0; /* [HGM] crash */
8095         return;
8096     }
8097
8098     /* If we're loading the game from a file, stop */
8099     if (whosays == GE_FILE) {
8100       (void) StopLoadGameTimer();
8101       gameFileFP = NULL;
8102     }
8103
8104     /* Cancel draw offers */
8105     first.offeredDraw = second.offeredDraw = 0;
8106
8107     /* If this is an ICS game, only ICS can really say it's done;
8108        if not, anyone can. */
8109     isIcsGame = (gameMode == IcsPlayingWhite || 
8110                  gameMode == IcsPlayingBlack || 
8111                  gameMode == IcsObserving    || 
8112                  gameMode == IcsExamining);
8113
8114     if (!isIcsGame || whosays == GE_ICS) {
8115         /* OK -- not an ICS game, or ICS said it was done */
8116         StopClocks();
8117         if (!isIcsGame && !appData.noChessProgram) 
8118           SetUserThinkingEnables();
8119     
8120         /* [HGM] if a machine claims the game end we verify this claim */
8121         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8122             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8123                 char claimer;
8124                 ChessMove trueResult = (ChessMove) -1;
8125
8126                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8127                                             first.twoMachinesColor[0] :
8128                                             second.twoMachinesColor[0] ;
8129
8130                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8131                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8132                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8133                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8134                 } else
8135                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8136                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8137                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8138                 } else
8139                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8140                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8141                 }
8142
8143                 // now verify win claims, but not in drop games, as we don't understand those yet
8144                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8145                                                  || gameInfo.variant == VariantGreat) &&
8146                     (result == WhiteWins && claimer == 'w' ||
8147                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8148                       if (appData.debugMode) {
8149                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8150                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8151                       }
8152                       if(result != trueResult) {
8153                               sprintf(buf, "False win claim: '%s'", resultDetails);
8154                               result = claimer == 'w' ? BlackWins : WhiteWins;
8155                               resultDetails = buf;
8156                       }
8157                 } else
8158                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8159                     && (forwardMostMove <= backwardMostMove ||
8160                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8161                         (claimer=='b')==(forwardMostMove&1))
8162                                                                                   ) {
8163                       /* [HGM] verify: draws that were not flagged are false claims */
8164                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8165                       result = claimer == 'w' ? BlackWins : WhiteWins;
8166                       resultDetails = buf;
8167                 }
8168                 /* (Claiming a loss is accepted no questions asked!) */
8169             }
8170             /* [HGM] bare: don't allow bare King to win */
8171             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8172                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8173                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8174                && result != GameIsDrawn)
8175             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8176                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8177                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8178                         if(p >= 0 && p <= (int)WhiteKing) k++;
8179                 }
8180                 if (appData.debugMode) {
8181                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8182                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8183                 }
8184                 if(k <= 1) {
8185                         result = GameIsDrawn;
8186                         sprintf(buf, "%s but bare king", resultDetails);
8187                         resultDetails = buf;
8188                 }
8189             }
8190         }
8191
8192
8193         if(serverMoves != NULL && !loadFlag) { char c = '=';
8194             if(result==WhiteWins) c = '+';
8195             if(result==BlackWins) c = '-';
8196             if(resultDetails != NULL)
8197                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8198         }
8199         if (resultDetails != NULL) {
8200             gameInfo.result = result;
8201             gameInfo.resultDetails = StrSave(resultDetails);
8202
8203             /* display last move only if game was not loaded from file */
8204             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8205                 DisplayMove(currentMove - 1);
8206     
8207             if (forwardMostMove != 0) {
8208                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8209                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8210                                                                 ) {
8211                     if (*appData.saveGameFile != NULLCHAR) {
8212                         SaveGameToFile(appData.saveGameFile, TRUE);
8213                     } else if (appData.autoSaveGames) {
8214                         AutoSaveGame();
8215                     }
8216                     if (*appData.savePositionFile != NULLCHAR) {
8217                         SavePositionToFile(appData.savePositionFile);
8218                     }
8219                 }
8220             }
8221
8222             /* Tell program how game ended in case it is learning */
8223             /* [HGM] Moved this to after saving the PGN, just in case */
8224             /* engine died and we got here through time loss. In that */
8225             /* case we will get a fatal error writing the pipe, which */
8226             /* would otherwise lose us the PGN.                       */
8227             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8228             /* output during GameEnds should never be fatal anymore   */
8229             if (gameMode == MachinePlaysWhite ||
8230                 gameMode == MachinePlaysBlack ||
8231                 gameMode == TwoMachinesPlay ||
8232                 gameMode == IcsPlayingWhite ||
8233                 gameMode == IcsPlayingBlack ||
8234                 gameMode == BeginningOfGame) {
8235                 char buf[MSG_SIZ];
8236                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8237                         resultDetails);
8238                 if (first.pr != NoProc) {
8239                     SendToProgram(buf, &first);
8240                 }
8241                 if (second.pr != NoProc &&
8242                     gameMode == TwoMachinesPlay) {
8243                     SendToProgram(buf, &second);
8244                 }
8245             }
8246         }
8247
8248         if (appData.icsActive) {
8249             if (appData.quietPlay &&
8250                 (gameMode == IcsPlayingWhite ||
8251                  gameMode == IcsPlayingBlack)) {
8252                 SendToICS(ics_prefix);
8253                 SendToICS("set shout 1\n");
8254             }
8255             nextGameMode = IcsIdle;
8256             ics_user_moved = FALSE;
8257             /* clean up premove.  It's ugly when the game has ended and the
8258              * premove highlights are still on the board.
8259              */
8260             if (gotPremove) {
8261               gotPremove = FALSE;
8262               ClearPremoveHighlights();
8263               DrawPosition(FALSE, boards[currentMove]);
8264             }
8265             if (whosays == GE_ICS) {
8266                 switch (result) {
8267                 case WhiteWins:
8268                     if (gameMode == IcsPlayingWhite)
8269                         PlayIcsWinSound();
8270                     else if(gameMode == IcsPlayingBlack)
8271                         PlayIcsLossSound();
8272                     break;
8273                 case BlackWins:
8274                     if (gameMode == IcsPlayingBlack)
8275                         PlayIcsWinSound();
8276                     else if(gameMode == IcsPlayingWhite)
8277                         PlayIcsLossSound();
8278                     break;
8279                 case GameIsDrawn:
8280                     PlayIcsDrawSound();
8281                     break;
8282                 default:
8283                     PlayIcsUnfinishedSound();
8284                 }
8285             }
8286         } else if (gameMode == EditGame ||
8287                    gameMode == PlayFromGameFile || 
8288                    gameMode == AnalyzeMode || 
8289                    gameMode == AnalyzeFile) {
8290             nextGameMode = gameMode;
8291         } else {
8292             nextGameMode = EndOfGame;
8293         }
8294         pausing = FALSE;
8295         ModeHighlight();
8296     } else {
8297         nextGameMode = gameMode;
8298     }
8299
8300     if (appData.noChessProgram) {
8301         gameMode = nextGameMode;
8302         ModeHighlight();
8303         endingGame = 0; /* [HGM] crash */
8304         return;
8305     }
8306
8307     if (first.reuse) {
8308         /* Put first chess program into idle state */
8309         if (first.pr != NoProc &&
8310             (gameMode == MachinePlaysWhite ||
8311              gameMode == MachinePlaysBlack ||
8312              gameMode == TwoMachinesPlay ||
8313              gameMode == IcsPlayingWhite ||
8314              gameMode == IcsPlayingBlack ||
8315              gameMode == BeginningOfGame)) {
8316             SendToProgram("force\n", &first);
8317             if (first.usePing) {
8318               char buf[MSG_SIZ];
8319               sprintf(buf, "ping %d\n", ++first.lastPing);
8320               SendToProgram(buf, &first);
8321             }
8322         }
8323     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8324         /* Kill off first chess program */
8325         if (first.isr != NULL)
8326           RemoveInputSource(first.isr);
8327         first.isr = NULL;
8328     
8329         if (first.pr != NoProc) {
8330             ExitAnalyzeMode();
8331             DoSleep( appData.delayBeforeQuit );
8332             SendToProgram("quit\n", &first);
8333             DoSleep( appData.delayAfterQuit );
8334             DestroyChildProcess(first.pr, first.useSigterm);
8335         }
8336         first.pr = NoProc;
8337     }
8338     if (second.reuse) {
8339         /* Put second chess program into idle state */
8340         if (second.pr != NoProc &&
8341             gameMode == TwoMachinesPlay) {
8342             SendToProgram("force\n", &second);
8343             if (second.usePing) {
8344               char buf[MSG_SIZ];
8345               sprintf(buf, "ping %d\n", ++second.lastPing);
8346               SendToProgram(buf, &second);
8347             }
8348         }
8349     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8350         /* Kill off second chess program */
8351         if (second.isr != NULL)
8352           RemoveInputSource(second.isr);
8353         second.isr = NULL;
8354     
8355         if (second.pr != NoProc) {
8356             DoSleep( appData.delayBeforeQuit );
8357             SendToProgram("quit\n", &second);
8358             DoSleep( appData.delayAfterQuit );
8359             DestroyChildProcess(second.pr, second.useSigterm);
8360         }
8361         second.pr = NoProc;
8362     }
8363
8364     if (matchMode && gameMode == TwoMachinesPlay) {
8365         switch (result) {
8366         case WhiteWins:
8367           if (first.twoMachinesColor[0] == 'w') {
8368             first.matchWins++;
8369           } else {
8370             second.matchWins++;
8371           }
8372           break;
8373         case BlackWins:
8374           if (first.twoMachinesColor[0] == 'b') {
8375             first.matchWins++;
8376           } else {
8377             second.matchWins++;
8378           }
8379           break;
8380         default:
8381           break;
8382         }
8383         if (matchGame < appData.matchGames) {
8384             char *tmp;
8385             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8386                 tmp = first.twoMachinesColor;
8387                 first.twoMachinesColor = second.twoMachinesColor;
8388                 second.twoMachinesColor = tmp;
8389             }
8390             gameMode = nextGameMode;
8391             matchGame++;
8392             if(appData.matchPause>10000 || appData.matchPause<10)
8393                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8394             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8395             endingGame = 0; /* [HGM] crash */
8396             return;
8397         } else {
8398             char buf[MSG_SIZ];
8399             gameMode = nextGameMode;
8400             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8401                     first.tidy, second.tidy,
8402                     first.matchWins, second.matchWins,
8403                     appData.matchGames - (first.matchWins + second.matchWins));
8404             DisplayFatalError(buf, 0, 0);
8405         }
8406     }
8407     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8408         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8409       ExitAnalyzeMode();
8410     gameMode = nextGameMode;
8411     ModeHighlight();
8412     endingGame = 0;  /* [HGM] crash */
8413 }
8414
8415 /* Assumes program was just initialized (initString sent).
8416    Leaves program in force mode. */
8417 void
8418 FeedMovesToProgram(cps, upto) 
8419      ChessProgramState *cps;
8420      int upto;
8421 {
8422     int i;
8423     
8424     if (appData.debugMode)
8425       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8426               startedFromSetupPosition ? "position and " : "",
8427               backwardMostMove, upto, cps->which);
8428     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8429         // [HGM] variantswitch: make engine aware of new variant
8430         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8431                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8432         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8433         SendToProgram(buf, cps);
8434         currentlyInitializedVariant = gameInfo.variant;
8435     }
8436     SendToProgram("force\n", cps);
8437     if (startedFromSetupPosition) {
8438         SendBoard(cps, backwardMostMove);
8439     if (appData.debugMode) {
8440         fprintf(debugFP, "feedMoves\n");
8441     }
8442     }
8443     for (i = backwardMostMove; i < upto; i++) {
8444         SendMoveToProgram(i, cps);
8445     }
8446 }
8447
8448
8449 void
8450 ResurrectChessProgram()
8451 {
8452      /* The chess program may have exited.
8453         If so, restart it and feed it all the moves made so far. */
8454
8455     if (appData.noChessProgram || first.pr != NoProc) return;
8456     
8457     StartChessProgram(&first);
8458     InitChessProgram(&first, FALSE);
8459     FeedMovesToProgram(&first, currentMove);
8460
8461     if (!first.sendTime) {
8462         /* can't tell gnuchess what its clock should read,
8463            so we bow to its notion. */
8464         ResetClocks();
8465         timeRemaining[0][currentMove] = whiteTimeRemaining;
8466         timeRemaining[1][currentMove] = blackTimeRemaining;
8467     }
8468
8469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8470                 appData.icsEngineAnalyze) && first.analysisSupport) {
8471       SendToProgram("analyze\n", &first);
8472       first.analyzing = TRUE;
8473     }
8474 }
8475
8476 /*
8477  * Button procedures
8478  */
8479 void
8480 Reset(redraw, init)
8481      int redraw, init;
8482 {
8483     int i;
8484
8485     if (appData.debugMode) {
8486         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8487                 redraw, init, gameMode);
8488     }
8489     CleanupTail(); // [HGM] vari: delete any stored variations
8490     pausing = pauseExamInvalid = FALSE;
8491     startedFromSetupPosition = blackPlaysFirst = FALSE;
8492     firstMove = TRUE;
8493     whiteFlag = blackFlag = FALSE;
8494     userOfferedDraw = FALSE;
8495     hintRequested = bookRequested = FALSE;
8496     first.maybeThinking = FALSE;
8497     second.maybeThinking = FALSE;
8498     first.bookSuspend = FALSE; // [HGM] book
8499     second.bookSuspend = FALSE;
8500     thinkOutput[0] = NULLCHAR;
8501     lastHint[0] = NULLCHAR;
8502     ClearGameInfo(&gameInfo);
8503     gameInfo.variant = StringToVariant(appData.variant);
8504     ics_user_moved = ics_clock_paused = FALSE;
8505     ics_getting_history = H_FALSE;
8506     ics_gamenum = -1;
8507     white_holding[0] = black_holding[0] = NULLCHAR;
8508     ClearProgramStats();
8509     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8510     
8511     ResetFrontEnd();
8512     ClearHighlights();
8513     flipView = appData.flipView;
8514     ClearPremoveHighlights();
8515     gotPremove = FALSE;
8516     alarmSounded = FALSE;
8517
8518     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8519     if(appData.serverMovesName != NULL) {
8520         /* [HGM] prepare to make moves file for broadcasting */
8521         clock_t t = clock();
8522         if(serverMoves != NULL) fclose(serverMoves);
8523         serverMoves = fopen(appData.serverMovesName, "r");
8524         if(serverMoves != NULL) {
8525             fclose(serverMoves);
8526             /* delay 15 sec before overwriting, so all clients can see end */
8527             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8528         }
8529         serverMoves = fopen(appData.serverMovesName, "w");
8530     }
8531
8532     ExitAnalyzeMode();
8533     gameMode = BeginningOfGame;
8534     ModeHighlight();
8535     if(appData.icsActive) gameInfo.variant = VariantNormal;
8536     currentMove = forwardMostMove = backwardMostMove = 0;
8537     InitPosition(redraw);
8538     for (i = 0; i < MAX_MOVES; i++) {
8539         if (commentList[i] != NULL) {
8540             free(commentList[i]);
8541             commentList[i] = NULL;
8542         }
8543     }
8544     ResetClocks();
8545     timeRemaining[0][0] = whiteTimeRemaining;
8546     timeRemaining[1][0] = blackTimeRemaining;
8547     if (first.pr == NULL) {
8548         StartChessProgram(&first);
8549     }
8550     if (init) {
8551             InitChessProgram(&first, startedFromSetupPosition);
8552     }
8553     DisplayTitle("");
8554     DisplayMessage("", "");
8555     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8556     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8557 }
8558
8559 void
8560 AutoPlayGameLoop()
8561 {
8562     for (;;) {
8563         if (!AutoPlayOneMove())
8564           return;
8565         if (matchMode || appData.timeDelay == 0)
8566           continue;
8567         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8568           return;
8569         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8570         break;
8571     }
8572 }
8573
8574
8575 int
8576 AutoPlayOneMove()
8577 {
8578     int fromX, fromY, toX, toY;
8579
8580     if (appData.debugMode) {
8581       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8582     }
8583
8584     if (gameMode != PlayFromGameFile)
8585       return FALSE;
8586
8587     if (currentMove >= forwardMostMove) {
8588       gameMode = EditGame;
8589       ModeHighlight();
8590
8591       /* [AS] Clear current move marker at the end of a game */
8592       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8593
8594       return FALSE;
8595     }
8596     
8597     toX = moveList[currentMove][2] - AAA;
8598     toY = moveList[currentMove][3] - ONE;
8599
8600     if (moveList[currentMove][1] == '@') {
8601         if (appData.highlightLastMove) {
8602             SetHighlights(-1, -1, toX, toY);
8603         }
8604     } else {
8605         fromX = moveList[currentMove][0] - AAA;
8606         fromY = moveList[currentMove][1] - ONE;
8607
8608         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8609
8610         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8611
8612         if (appData.highlightLastMove) {
8613             SetHighlights(fromX, fromY, toX, toY);
8614         }
8615     }
8616     DisplayMove(currentMove);
8617     SendMoveToProgram(currentMove++, &first);
8618     DisplayBothClocks();
8619     DrawPosition(FALSE, boards[currentMove]);
8620     // [HGM] PV info: always display, routine tests if empty
8621     DisplayComment(currentMove - 1, commentList[currentMove]);
8622     return TRUE;
8623 }
8624
8625
8626 int
8627 LoadGameOneMove(readAhead)
8628      ChessMove readAhead;
8629 {
8630     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8631     char promoChar = NULLCHAR;
8632     ChessMove moveType;
8633     char move[MSG_SIZ];
8634     char *p, *q;
8635     
8636     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8637         gameMode != AnalyzeMode && gameMode != Training) {
8638         gameFileFP = NULL;
8639         return FALSE;
8640     }
8641     
8642     yyboardindex = forwardMostMove;
8643     if (readAhead != (ChessMove)0) {
8644       moveType = readAhead;
8645     } else {
8646       if (gameFileFP == NULL)
8647           return FALSE;
8648       moveType = (ChessMove) yylex();
8649     }
8650     
8651     done = FALSE;
8652     switch (moveType) {
8653       case Comment:
8654         if (appData.debugMode) 
8655           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8656         p = yy_text;
8657
8658         /* append the comment but don't display it */
8659         AppendComment(currentMove, p, FALSE);
8660         return TRUE;
8661
8662       case WhiteCapturesEnPassant:
8663       case BlackCapturesEnPassant:
8664       case WhitePromotionChancellor:
8665       case BlackPromotionChancellor:
8666       case WhitePromotionArchbishop:
8667       case BlackPromotionArchbishop:
8668       case WhitePromotionCentaur:
8669       case BlackPromotionCentaur:
8670       case WhitePromotionQueen:
8671       case BlackPromotionQueen:
8672       case WhitePromotionRook:
8673       case BlackPromotionRook:
8674       case WhitePromotionBishop:
8675       case BlackPromotionBishop:
8676       case WhitePromotionKnight:
8677       case BlackPromotionKnight:
8678       case WhitePromotionKing:
8679       case BlackPromotionKing:
8680       case NormalMove:
8681       case WhiteKingSideCastle:
8682       case WhiteQueenSideCastle:
8683       case BlackKingSideCastle:
8684       case BlackQueenSideCastle:
8685       case WhiteKingSideCastleWild:
8686       case WhiteQueenSideCastleWild:
8687       case BlackKingSideCastleWild:
8688       case BlackQueenSideCastleWild:
8689       /* PUSH Fabien */
8690       case WhiteHSideCastleFR:
8691       case WhiteASideCastleFR:
8692       case BlackHSideCastleFR:
8693       case BlackASideCastleFR:
8694       /* POP Fabien */
8695         if (appData.debugMode)
8696           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8697         fromX = currentMoveString[0] - AAA;
8698         fromY = currentMoveString[1] - ONE;
8699         toX = currentMoveString[2] - AAA;
8700         toY = currentMoveString[3] - ONE;
8701         promoChar = currentMoveString[4];
8702         break;
8703
8704       case WhiteDrop:
8705       case BlackDrop:
8706         if (appData.debugMode)
8707           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8708         fromX = moveType == WhiteDrop ?
8709           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8710         (int) CharToPiece(ToLower(currentMoveString[0]));
8711         fromY = DROP_RANK;
8712         toX = currentMoveString[2] - AAA;
8713         toY = currentMoveString[3] - ONE;
8714         break;
8715
8716       case WhiteWins:
8717       case BlackWins:
8718       case GameIsDrawn:
8719       case GameUnfinished:
8720         if (appData.debugMode)
8721           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8722         p = strchr(yy_text, '{');
8723         if (p == NULL) p = strchr(yy_text, '(');
8724         if (p == NULL) {
8725             p = yy_text;
8726             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8727         } else {
8728             q = strchr(p, *p == '{' ? '}' : ')');
8729             if (q != NULL) *q = NULLCHAR;
8730             p++;
8731         }
8732         GameEnds(moveType, p, GE_FILE);
8733         done = TRUE;
8734         if (cmailMsgLoaded) {
8735             ClearHighlights();
8736             flipView = WhiteOnMove(currentMove);
8737             if (moveType == GameUnfinished) flipView = !flipView;
8738             if (appData.debugMode)
8739               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8740         }
8741         break;
8742
8743       case (ChessMove) 0:       /* end of file */
8744         if (appData.debugMode)
8745           fprintf(debugFP, "Parser hit end of file\n");
8746         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8747           case MT_NONE:
8748           case MT_CHECK:
8749             break;
8750           case MT_CHECKMATE:
8751           case MT_STAINMATE:
8752             if (WhiteOnMove(currentMove)) {
8753                 GameEnds(BlackWins, "Black mates", GE_FILE);
8754             } else {
8755                 GameEnds(WhiteWins, "White mates", GE_FILE);
8756             }
8757             break;
8758           case MT_STALEMATE:
8759             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8760             break;
8761         }
8762         done = TRUE;
8763         break;
8764
8765       case MoveNumberOne:
8766         if (lastLoadGameStart == GNUChessGame) {
8767             /* GNUChessGames have numbers, but they aren't move numbers */
8768             if (appData.debugMode)
8769               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8770                       yy_text, (int) moveType);
8771             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8772         }
8773         /* else fall thru */
8774
8775       case XBoardGame:
8776       case GNUChessGame:
8777       case PGNTag:
8778         /* Reached start of next game in file */
8779         if (appData.debugMode)
8780           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8781         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8782           case MT_NONE:
8783           case MT_CHECK:
8784             break;
8785           case MT_CHECKMATE:
8786           case MT_STAINMATE:
8787             if (WhiteOnMove(currentMove)) {
8788                 GameEnds(BlackWins, "Black mates", GE_FILE);
8789             } else {
8790                 GameEnds(WhiteWins, "White mates", GE_FILE);
8791             }
8792             break;
8793           case MT_STALEMATE:
8794             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8795             break;
8796         }
8797         done = TRUE;
8798         break;
8799
8800       case PositionDiagram:     /* should not happen; ignore */
8801       case ElapsedTime:         /* ignore */
8802       case NAG:                 /* ignore */
8803         if (appData.debugMode)
8804           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8805                   yy_text, (int) moveType);
8806         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8807
8808       case IllegalMove:
8809         if (appData.testLegality) {
8810             if (appData.debugMode)
8811               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8812             sprintf(move, _("Illegal move: %d.%s%s"),
8813                     (forwardMostMove / 2) + 1,
8814                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8815             DisplayError(move, 0);
8816             done = TRUE;
8817         } else {
8818             if (appData.debugMode)
8819               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8820                       yy_text, currentMoveString);
8821             fromX = currentMoveString[0] - AAA;
8822             fromY = currentMoveString[1] - ONE;
8823             toX = currentMoveString[2] - AAA;
8824             toY = currentMoveString[3] - ONE;
8825             promoChar = currentMoveString[4];
8826         }
8827         break;
8828
8829       case AmbiguousMove:
8830         if (appData.debugMode)
8831           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8832         sprintf(move, _("Ambiguous move: %d.%s%s"),
8833                 (forwardMostMove / 2) + 1,
8834                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8835         DisplayError(move, 0);
8836         done = TRUE;
8837         break;
8838
8839       default:
8840       case ImpossibleMove:
8841         if (appData.debugMode)
8842           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8843         sprintf(move, _("Illegal move: %d.%s%s"),
8844                 (forwardMostMove / 2) + 1,
8845                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8846         DisplayError(move, 0);
8847         done = TRUE;
8848         break;
8849     }
8850
8851     if (done) {
8852         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8853             DrawPosition(FALSE, boards[currentMove]);
8854             DisplayBothClocks();
8855             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8856               DisplayComment(currentMove - 1, commentList[currentMove]);
8857         }
8858         (void) StopLoadGameTimer();
8859         gameFileFP = NULL;
8860         cmailOldMove = forwardMostMove;
8861         return FALSE;
8862     } else {
8863         /* currentMoveString is set as a side-effect of yylex */
8864         strcat(currentMoveString, "\n");
8865         strcpy(moveList[forwardMostMove], currentMoveString);
8866         
8867         thinkOutput[0] = NULLCHAR;
8868         MakeMove(fromX, fromY, toX, toY, promoChar);
8869         currentMove = forwardMostMove;
8870         return TRUE;
8871     }
8872 }
8873
8874 /* Load the nth game from the given file */
8875 int
8876 LoadGameFromFile(filename, n, title, useList)
8877      char *filename;
8878      int n;
8879      char *title;
8880      /*Boolean*/ int useList;
8881 {
8882     FILE *f;
8883     char buf[MSG_SIZ];
8884
8885     if (strcmp(filename, "-") == 0) {
8886         f = stdin;
8887         title = "stdin";
8888     } else {
8889         f = fopen(filename, "rb");
8890         if (f == NULL) {
8891           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8892             DisplayError(buf, errno);
8893             return FALSE;
8894         }
8895     }
8896     if (fseek(f, 0, 0) == -1) {
8897         /* f is not seekable; probably a pipe */
8898         useList = FALSE;
8899     }
8900     if (useList && n == 0) {
8901         int error = GameListBuild(f);
8902         if (error) {
8903             DisplayError(_("Cannot build game list"), error);
8904         } else if (!ListEmpty(&gameList) &&
8905                    ((ListGame *) gameList.tailPred)->number > 1) {
8906             GameListPopUp(f, title);
8907             return TRUE;
8908         }
8909         GameListDestroy();
8910         n = 1;
8911     }
8912     if (n == 0) n = 1;
8913     return LoadGame(f, n, title, FALSE);
8914 }
8915
8916
8917 void
8918 MakeRegisteredMove()
8919 {
8920     int fromX, fromY, toX, toY;
8921     char promoChar;
8922     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8923         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8924           case CMAIL_MOVE:
8925           case CMAIL_DRAW:
8926             if (appData.debugMode)
8927               fprintf(debugFP, "Restoring %s for game %d\n",
8928                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8929     
8930             thinkOutput[0] = NULLCHAR;
8931             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8932             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8933             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8934             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8935             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8936             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8937             MakeMove(fromX, fromY, toX, toY, promoChar);
8938             ShowMove(fromX, fromY, toX, toY);
8939               
8940             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8941               case MT_NONE:
8942               case MT_CHECK:
8943                 break;
8944                 
8945               case MT_CHECKMATE:
8946               case MT_STAINMATE:
8947                 if (WhiteOnMove(currentMove)) {
8948                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8949                 } else {
8950                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8951                 }
8952                 break;
8953                 
8954               case MT_STALEMATE:
8955                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8956                 break;
8957             }
8958
8959             break;
8960             
8961           case CMAIL_RESIGN:
8962             if (WhiteOnMove(currentMove)) {
8963                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8964             } else {
8965                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8966             }
8967             break;
8968             
8969           case CMAIL_ACCEPT:
8970             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8971             break;
8972               
8973           default:
8974             break;
8975         }
8976     }
8977
8978     return;
8979 }
8980
8981 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8982 int
8983 CmailLoadGame(f, gameNumber, title, useList)
8984      FILE *f;
8985      int gameNumber;
8986      char *title;
8987      int useList;
8988 {
8989     int retVal;
8990
8991     if (gameNumber > nCmailGames) {
8992         DisplayError(_("No more games in this message"), 0);
8993         return FALSE;
8994     }
8995     if (f == lastLoadGameFP) {
8996         int offset = gameNumber - lastLoadGameNumber;
8997         if (offset == 0) {
8998             cmailMsg[0] = NULLCHAR;
8999             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9000                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9001                 nCmailMovesRegistered--;
9002             }
9003             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9004             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9005                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9006             }
9007         } else {
9008             if (! RegisterMove()) return FALSE;
9009         }
9010     }
9011
9012     retVal = LoadGame(f, gameNumber, title, useList);
9013
9014     /* Make move registered during previous look at this game, if any */
9015     MakeRegisteredMove();
9016
9017     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9018         commentList[currentMove]
9019           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9020         DisplayComment(currentMove - 1, commentList[currentMove]);
9021     }
9022
9023     return retVal;
9024 }
9025
9026 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9027 int
9028 ReloadGame(offset)
9029      int offset;
9030 {
9031     int gameNumber = lastLoadGameNumber + offset;
9032     if (lastLoadGameFP == NULL) {
9033         DisplayError(_("No game has been loaded yet"), 0);
9034         return FALSE;
9035     }
9036     if (gameNumber <= 0) {
9037         DisplayError(_("Can't back up any further"), 0);
9038         return FALSE;
9039     }
9040     if (cmailMsgLoaded) {
9041         return CmailLoadGame(lastLoadGameFP, gameNumber,
9042                              lastLoadGameTitle, lastLoadGameUseList);
9043     } else {
9044         return LoadGame(lastLoadGameFP, gameNumber,
9045                         lastLoadGameTitle, lastLoadGameUseList);
9046     }
9047 }
9048
9049
9050
9051 /* Load the nth game from open file f */
9052 int
9053 LoadGame(f, gameNumber, title, useList)
9054      FILE *f;
9055      int gameNumber;
9056      char *title;
9057      int useList;
9058 {
9059     ChessMove cm;
9060     char buf[MSG_SIZ];
9061     int gn = gameNumber;
9062     ListGame *lg = NULL;
9063     int numPGNTags = 0;
9064     int err;
9065     GameMode oldGameMode;
9066     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9067
9068     if (appData.debugMode) 
9069         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9070
9071     if (gameMode == Training )
9072         SetTrainingModeOff();
9073
9074     oldGameMode = gameMode;
9075     if (gameMode != BeginningOfGame) {
9076       Reset(FALSE, TRUE);
9077     }
9078
9079     gameFileFP = f;
9080     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9081         fclose(lastLoadGameFP);
9082     }
9083
9084     if (useList) {
9085         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9086         
9087         if (lg) {
9088             fseek(f, lg->offset, 0);
9089             GameListHighlight(gameNumber);
9090             gn = 1;
9091         }
9092         else {
9093             DisplayError(_("Game number out of range"), 0);
9094             return FALSE;
9095         }
9096     } else {
9097         GameListDestroy();
9098         if (fseek(f, 0, 0) == -1) {
9099             if (f == lastLoadGameFP ?
9100                 gameNumber == lastLoadGameNumber + 1 :
9101                 gameNumber == 1) {
9102                 gn = 1;
9103             } else {
9104                 DisplayError(_("Can't seek on game file"), 0);
9105                 return FALSE;
9106             }
9107         }
9108     }
9109     lastLoadGameFP = f;
9110     lastLoadGameNumber = gameNumber;
9111     strcpy(lastLoadGameTitle, title);
9112     lastLoadGameUseList = useList;
9113
9114     yynewfile(f);
9115
9116     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9117       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9118                 lg->gameInfo.black);
9119             DisplayTitle(buf);
9120     } else if (*title != NULLCHAR) {
9121         if (gameNumber > 1) {
9122             sprintf(buf, "%s %d", title, gameNumber);
9123             DisplayTitle(buf);
9124         } else {
9125             DisplayTitle(title);
9126         }
9127     }
9128
9129     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9130         gameMode = PlayFromGameFile;
9131         ModeHighlight();
9132     }
9133
9134     currentMove = forwardMostMove = backwardMostMove = 0;
9135     CopyBoard(boards[0], initialPosition);
9136     StopClocks();
9137
9138     /*
9139      * Skip the first gn-1 games in the file.
9140      * Also skip over anything that precedes an identifiable 
9141      * start of game marker, to avoid being confused by 
9142      * garbage at the start of the file.  Currently 
9143      * recognized start of game markers are the move number "1",
9144      * the pattern "gnuchess .* game", the pattern
9145      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9146      * A game that starts with one of the latter two patterns
9147      * will also have a move number 1, possibly
9148      * following a position diagram.
9149      * 5-4-02: Let's try being more lenient and allowing a game to
9150      * start with an unnumbered move.  Does that break anything?
9151      */
9152     cm = lastLoadGameStart = (ChessMove) 0;
9153     while (gn > 0) {
9154         yyboardindex = forwardMostMove;
9155         cm = (ChessMove) yylex();
9156         switch (cm) {
9157           case (ChessMove) 0:
9158             if (cmailMsgLoaded) {
9159                 nCmailGames = CMAIL_MAX_GAMES - gn;
9160             } else {
9161                 Reset(TRUE, TRUE);
9162                 DisplayError(_("Game not found in file"), 0);
9163             }
9164             return FALSE;
9165
9166           case GNUChessGame:
9167           case XBoardGame:
9168             gn--;
9169             lastLoadGameStart = cm;
9170             break;
9171             
9172           case MoveNumberOne:
9173             switch (lastLoadGameStart) {
9174               case GNUChessGame:
9175               case XBoardGame:
9176               case PGNTag:
9177                 break;
9178               case MoveNumberOne:
9179               case (ChessMove) 0:
9180                 gn--;           /* count this game */
9181                 lastLoadGameStart = cm;
9182                 break;
9183               default:
9184                 /* impossible */
9185                 break;
9186             }
9187             break;
9188
9189           case PGNTag:
9190             switch (lastLoadGameStart) {
9191               case GNUChessGame:
9192               case PGNTag:
9193               case MoveNumberOne:
9194               case (ChessMove) 0:
9195                 gn--;           /* count this game */
9196                 lastLoadGameStart = cm;
9197                 break;
9198               case XBoardGame:
9199                 lastLoadGameStart = cm; /* game counted already */
9200                 break;
9201               default:
9202                 /* impossible */
9203                 break;
9204             }
9205             if (gn > 0) {
9206                 do {
9207                     yyboardindex = forwardMostMove;
9208                     cm = (ChessMove) yylex();
9209                 } while (cm == PGNTag || cm == Comment);
9210             }
9211             break;
9212
9213           case WhiteWins:
9214           case BlackWins:
9215           case GameIsDrawn:
9216             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9217                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9218                     != CMAIL_OLD_RESULT) {
9219                     nCmailResults ++ ;
9220                     cmailResult[  CMAIL_MAX_GAMES
9221                                 - gn - 1] = CMAIL_OLD_RESULT;
9222                 }
9223             }
9224             break;
9225
9226           case NormalMove:
9227             /* Only a NormalMove can be at the start of a game
9228              * without a position diagram. */
9229             if (lastLoadGameStart == (ChessMove) 0) {
9230               gn--;
9231               lastLoadGameStart = MoveNumberOne;
9232             }
9233             break;
9234
9235           default:
9236             break;
9237         }
9238     }
9239     
9240     if (appData.debugMode)
9241       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9242
9243     if (cm == XBoardGame) {
9244         /* Skip any header junk before position diagram and/or move 1 */
9245         for (;;) {
9246             yyboardindex = forwardMostMove;
9247             cm = (ChessMove) yylex();
9248
9249             if (cm == (ChessMove) 0 ||
9250                 cm == GNUChessGame || cm == XBoardGame) {
9251                 /* Empty game; pretend end-of-file and handle later */
9252                 cm = (ChessMove) 0;
9253                 break;
9254             }
9255
9256             if (cm == MoveNumberOne || cm == PositionDiagram ||
9257                 cm == PGNTag || cm == Comment)
9258               break;
9259         }
9260     } else if (cm == GNUChessGame) {
9261         if (gameInfo.event != NULL) {
9262             free(gameInfo.event);
9263         }
9264         gameInfo.event = StrSave(yy_text);
9265     }   
9266
9267     startedFromSetupPosition = FALSE;
9268     while (cm == PGNTag) {
9269         if (appData.debugMode) 
9270           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9271         err = ParsePGNTag(yy_text, &gameInfo);
9272         if (!err) numPGNTags++;
9273
9274         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9275         if(gameInfo.variant != oldVariant) {
9276             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9277             InitPosition(TRUE);
9278             oldVariant = gameInfo.variant;
9279             if (appData.debugMode) 
9280               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9281         }
9282
9283
9284         if (gameInfo.fen != NULL) {
9285           Board initial_position;
9286           startedFromSetupPosition = TRUE;
9287           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9288             Reset(TRUE, TRUE);
9289             DisplayError(_("Bad FEN position in file"), 0);
9290             return FALSE;
9291           }
9292           CopyBoard(boards[0], initial_position);
9293           if (blackPlaysFirst) {
9294             currentMove = forwardMostMove = backwardMostMove = 1;
9295             CopyBoard(boards[1], initial_position);
9296             strcpy(moveList[0], "");
9297             strcpy(parseList[0], "");
9298             timeRemaining[0][1] = whiteTimeRemaining;
9299             timeRemaining[1][1] = blackTimeRemaining;
9300             if (commentList[0] != NULL) {
9301               commentList[1] = commentList[0];
9302               commentList[0] = NULL;
9303             }
9304           } else {
9305             currentMove = forwardMostMove = backwardMostMove = 0;
9306           }
9307           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9308           {   int i;
9309               initialRulePlies = FENrulePlies;
9310               for( i=0; i< nrCastlingRights; i++ )
9311                   initialRights[i] = initial_position[CASTLING][i];
9312           }
9313           yyboardindex = forwardMostMove;
9314           free(gameInfo.fen);
9315           gameInfo.fen = NULL;
9316         }
9317
9318         yyboardindex = forwardMostMove;
9319         cm = (ChessMove) yylex();
9320
9321         /* Handle comments interspersed among the tags */
9322         while (cm == Comment) {
9323             char *p;
9324             if (appData.debugMode) 
9325               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9326             p = yy_text;
9327             AppendComment(currentMove, p, FALSE);
9328             yyboardindex = forwardMostMove;
9329             cm = (ChessMove) yylex();
9330         }
9331     }
9332
9333     /* don't rely on existence of Event tag since if game was
9334      * pasted from clipboard the Event tag may not exist
9335      */
9336     if (numPGNTags > 0){
9337         char *tags;
9338         if (gameInfo.variant == VariantNormal) {
9339           gameInfo.variant = StringToVariant(gameInfo.event);
9340         }
9341         if (!matchMode) {
9342           if( appData.autoDisplayTags ) {
9343             tags = PGNTags(&gameInfo);
9344             TagsPopUp(tags, CmailMsg());
9345             free(tags);
9346           }
9347         }
9348     } else {
9349         /* Make something up, but don't display it now */
9350         SetGameInfo();
9351         TagsPopDown();
9352     }
9353
9354     if (cm == PositionDiagram) {
9355         int i, j;
9356         char *p;
9357         Board initial_position;
9358
9359         if (appData.debugMode)
9360           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9361
9362         if (!startedFromSetupPosition) {
9363             p = yy_text;
9364             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9365               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9366                 switch (*p) {
9367                   case '[':
9368                   case '-':
9369                   case ' ':
9370                   case '\t':
9371                   case '\n':
9372                   case '\r':
9373                     break;
9374                   default:
9375                     initial_position[i][j++] = CharToPiece(*p);
9376                     break;
9377                 }
9378             while (*p == ' ' || *p == '\t' ||
9379                    *p == '\n' || *p == '\r') p++;
9380         
9381             if (strncmp(p, "black", strlen("black"))==0)
9382               blackPlaysFirst = TRUE;
9383             else
9384               blackPlaysFirst = FALSE;
9385             startedFromSetupPosition = TRUE;
9386         
9387             CopyBoard(boards[0], initial_position);
9388             if (blackPlaysFirst) {
9389                 currentMove = forwardMostMove = backwardMostMove = 1;
9390                 CopyBoard(boards[1], initial_position);
9391                 strcpy(moveList[0], "");
9392                 strcpy(parseList[0], "");
9393                 timeRemaining[0][1] = whiteTimeRemaining;
9394                 timeRemaining[1][1] = blackTimeRemaining;
9395                 if (commentList[0] != NULL) {
9396                     commentList[1] = commentList[0];
9397                     commentList[0] = NULL;
9398                 }
9399             } else {
9400                 currentMove = forwardMostMove = backwardMostMove = 0;
9401             }
9402         }
9403         yyboardindex = forwardMostMove;
9404         cm = (ChessMove) yylex();
9405     }
9406
9407     if (first.pr == NoProc) {
9408         StartChessProgram(&first);
9409     }
9410     InitChessProgram(&first, FALSE);
9411     SendToProgram("force\n", &first);
9412     if (startedFromSetupPosition) {
9413         SendBoard(&first, forwardMostMove);
9414     if (appData.debugMode) {
9415         fprintf(debugFP, "Load Game\n");
9416     }
9417         DisplayBothClocks();
9418     }      
9419
9420     /* [HGM] server: flag to write setup moves in broadcast file as one */
9421     loadFlag = appData.suppressLoadMoves;
9422
9423     while (cm == Comment) {
9424         char *p;
9425         if (appData.debugMode) 
9426           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9427         p = yy_text;
9428         AppendComment(currentMove, p, FALSE);
9429         yyboardindex = forwardMostMove;
9430         cm = (ChessMove) yylex();
9431     }
9432
9433     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9434         cm == WhiteWins || cm == BlackWins ||
9435         cm == GameIsDrawn || cm == GameUnfinished) {
9436         DisplayMessage("", _("No moves in game"));
9437         if (cmailMsgLoaded) {
9438             if (appData.debugMode)
9439               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9440             ClearHighlights();
9441             flipView = FALSE;
9442         }
9443         DrawPosition(FALSE, boards[currentMove]);
9444         DisplayBothClocks();
9445         gameMode = EditGame;
9446         ModeHighlight();
9447         gameFileFP = NULL;
9448         cmailOldMove = 0;
9449         return TRUE;
9450     }
9451
9452     // [HGM] PV info: routine tests if comment empty
9453     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9454         DisplayComment(currentMove - 1, commentList[currentMove]);
9455     }
9456     if (!matchMode && appData.timeDelay != 0) 
9457       DrawPosition(FALSE, boards[currentMove]);
9458
9459     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9460       programStats.ok_to_send = 1;
9461     }
9462
9463     /* if the first token after the PGN tags is a move
9464      * and not move number 1, retrieve it from the parser 
9465      */
9466     if (cm != MoveNumberOne)
9467         LoadGameOneMove(cm);
9468
9469     /* load the remaining moves from the file */
9470     while (LoadGameOneMove((ChessMove)0)) {
9471       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9472       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9473     }
9474
9475     /* rewind to the start of the game */
9476     currentMove = backwardMostMove;
9477
9478     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9479
9480     if (oldGameMode == AnalyzeFile ||
9481         oldGameMode == AnalyzeMode) {
9482       AnalyzeFileEvent();
9483     }
9484
9485     if (matchMode || appData.timeDelay == 0) {
9486       ToEndEvent();
9487       gameMode = EditGame;
9488       ModeHighlight();
9489     } else if (appData.timeDelay > 0) {
9490       AutoPlayGameLoop();
9491     }
9492
9493     if (appData.debugMode) 
9494         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9495
9496     loadFlag = 0; /* [HGM] true game starts */
9497     return TRUE;
9498 }
9499
9500 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9501 int
9502 ReloadPosition(offset)
9503      int offset;
9504 {
9505     int positionNumber = lastLoadPositionNumber + offset;
9506     if (lastLoadPositionFP == NULL) {
9507         DisplayError(_("No position has been loaded yet"), 0);
9508         return FALSE;
9509     }
9510     if (positionNumber <= 0) {
9511         DisplayError(_("Can't back up any further"), 0);
9512         return FALSE;
9513     }
9514     return LoadPosition(lastLoadPositionFP, positionNumber,
9515                         lastLoadPositionTitle);
9516 }
9517
9518 /* Load the nth position from the given file */
9519 int
9520 LoadPositionFromFile(filename, n, title)
9521      char *filename;
9522      int n;
9523      char *title;
9524 {
9525     FILE *f;
9526     char buf[MSG_SIZ];
9527
9528     if (strcmp(filename, "-") == 0) {
9529         return LoadPosition(stdin, n, "stdin");
9530     } else {
9531         f = fopen(filename, "rb");
9532         if (f == NULL) {
9533             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9534             DisplayError(buf, errno);
9535             return FALSE;
9536         } else {
9537             return LoadPosition(f, n, title);
9538         }
9539     }
9540 }
9541
9542 /* Load the nth position from the given open file, and close it */
9543 int
9544 LoadPosition(f, positionNumber, title)
9545      FILE *f;
9546      int positionNumber;
9547      char *title;
9548 {
9549     char *p, line[MSG_SIZ];
9550     Board initial_position;
9551     int i, j, fenMode, pn;
9552     
9553     if (gameMode == Training )
9554         SetTrainingModeOff();
9555
9556     if (gameMode != BeginningOfGame) {
9557         Reset(FALSE, TRUE);
9558     }
9559     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9560         fclose(lastLoadPositionFP);
9561     }
9562     if (positionNumber == 0) positionNumber = 1;
9563     lastLoadPositionFP = f;
9564     lastLoadPositionNumber = positionNumber;
9565     strcpy(lastLoadPositionTitle, title);
9566     if (first.pr == NoProc) {
9567       StartChessProgram(&first);
9568       InitChessProgram(&first, FALSE);
9569     }    
9570     pn = positionNumber;
9571     if (positionNumber < 0) {
9572         /* Negative position number means to seek to that byte offset */
9573         if (fseek(f, -positionNumber, 0) == -1) {
9574             DisplayError(_("Can't seek on position file"), 0);
9575             return FALSE;
9576         };
9577         pn = 1;
9578     } else {
9579         if (fseek(f, 0, 0) == -1) {
9580             if (f == lastLoadPositionFP ?
9581                 positionNumber == lastLoadPositionNumber + 1 :
9582                 positionNumber == 1) {
9583                 pn = 1;
9584             } else {
9585                 DisplayError(_("Can't seek on position file"), 0);
9586                 return FALSE;
9587             }
9588         }
9589     }
9590     /* See if this file is FEN or old-style xboard */
9591     if (fgets(line, MSG_SIZ, f) == NULL) {
9592         DisplayError(_("Position not found in file"), 0);
9593         return FALSE;
9594     }
9595     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9596     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9597
9598     if (pn >= 2) {
9599         if (fenMode || line[0] == '#') pn--;
9600         while (pn > 0) {
9601             /* skip positions before number pn */
9602             if (fgets(line, MSG_SIZ, f) == NULL) {
9603                 Reset(TRUE, TRUE);
9604                 DisplayError(_("Position not found in file"), 0);
9605                 return FALSE;
9606             }
9607             if (fenMode || line[0] == '#') pn--;
9608         }
9609     }
9610
9611     if (fenMode) {
9612         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9613             DisplayError(_("Bad FEN position in file"), 0);
9614             return FALSE;
9615         }
9616     } else {
9617         (void) fgets(line, MSG_SIZ, f);
9618         (void) fgets(line, MSG_SIZ, f);
9619     
9620         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9621             (void) fgets(line, MSG_SIZ, f);
9622             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9623                 if (*p == ' ')
9624                   continue;
9625                 initial_position[i][j++] = CharToPiece(*p);
9626             }
9627         }
9628     
9629         blackPlaysFirst = FALSE;
9630         if (!feof(f)) {
9631             (void) fgets(line, MSG_SIZ, f);
9632             if (strncmp(line, "black", strlen("black"))==0)
9633               blackPlaysFirst = TRUE;
9634         }
9635     }
9636     startedFromSetupPosition = TRUE;
9637     
9638     SendToProgram("force\n", &first);
9639     CopyBoard(boards[0], initial_position);
9640     if (blackPlaysFirst) {
9641         currentMove = forwardMostMove = backwardMostMove = 1;
9642         strcpy(moveList[0], "");
9643         strcpy(parseList[0], "");
9644         CopyBoard(boards[1], initial_position);
9645         DisplayMessage("", _("Black to play"));
9646     } else {
9647         currentMove = forwardMostMove = backwardMostMove = 0;
9648         DisplayMessage("", _("White to play"));
9649     }
9650     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9651     SendBoard(&first, forwardMostMove);
9652     if (appData.debugMode) {
9653 int i, j;
9654   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9655   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9656         fprintf(debugFP, "Load Position\n");
9657     }
9658
9659     if (positionNumber > 1) {
9660         sprintf(line, "%s %d", title, positionNumber);
9661         DisplayTitle(line);
9662     } else {
9663         DisplayTitle(title);
9664     }
9665     gameMode = EditGame;
9666     ModeHighlight();
9667     ResetClocks();
9668     timeRemaining[0][1] = whiteTimeRemaining;
9669     timeRemaining[1][1] = blackTimeRemaining;
9670     DrawPosition(FALSE, boards[currentMove]);
9671    
9672     return TRUE;
9673 }
9674
9675
9676 void
9677 CopyPlayerNameIntoFileName(dest, src)
9678      char **dest, *src;
9679 {
9680     while (*src != NULLCHAR && *src != ',') {
9681         if (*src == ' ') {
9682             *(*dest)++ = '_';
9683             src++;
9684         } else {
9685             *(*dest)++ = *src++;
9686         }
9687     }
9688 }
9689
9690 char *DefaultFileName(ext)
9691      char *ext;
9692 {
9693     static char def[MSG_SIZ];
9694     char *p;
9695
9696     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9697         p = def;
9698         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9699         *p++ = '-';
9700         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9701         *p++ = '.';
9702         strcpy(p, ext);
9703     } else {
9704         def[0] = NULLCHAR;
9705     }
9706     return def;
9707 }
9708
9709 /* Save the current game to the given file */
9710 int
9711 SaveGameToFile(filename, append)
9712      char *filename;
9713      int append;
9714 {
9715     FILE *f;
9716     char buf[MSG_SIZ];
9717
9718     if (strcmp(filename, "-") == 0) {
9719         return SaveGame(stdout, 0, NULL);
9720     } else {
9721         f = fopen(filename, append ? "a" : "w");
9722         if (f == NULL) {
9723             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9724             DisplayError(buf, errno);
9725             return FALSE;
9726         } else {
9727             return SaveGame(f, 0, NULL);
9728         }
9729     }
9730 }
9731
9732 char *
9733 SavePart(str)
9734      char *str;
9735 {
9736     static char buf[MSG_SIZ];
9737     char *p;
9738     
9739     p = strchr(str, ' ');
9740     if (p == NULL) return str;
9741     strncpy(buf, str, p - str);
9742     buf[p - str] = NULLCHAR;
9743     return buf;
9744 }
9745
9746 #define PGN_MAX_LINE 75
9747
9748 #define PGN_SIDE_WHITE  0
9749 #define PGN_SIDE_BLACK  1
9750
9751 /* [AS] */
9752 static int FindFirstMoveOutOfBook( int side )
9753 {
9754     int result = -1;
9755
9756     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9757         int index = backwardMostMove;
9758         int has_book_hit = 0;
9759
9760         if( (index % 2) != side ) {
9761             index++;
9762         }
9763
9764         while( index < forwardMostMove ) {
9765             /* Check to see if engine is in book */
9766             int depth = pvInfoList[index].depth;
9767             int score = pvInfoList[index].score;
9768             int in_book = 0;
9769
9770             if( depth <= 2 ) {
9771                 in_book = 1;
9772             }
9773             else if( score == 0 && depth == 63 ) {
9774                 in_book = 1; /* Zappa */
9775             }
9776             else if( score == 2 && depth == 99 ) {
9777                 in_book = 1; /* Abrok */
9778             }
9779
9780             has_book_hit += in_book;
9781
9782             if( ! in_book ) {
9783                 result = index;
9784
9785                 break;
9786             }
9787
9788             index += 2;
9789         }
9790     }
9791
9792     return result;
9793 }
9794
9795 /* [AS] */
9796 void GetOutOfBookInfo( char * buf )
9797 {
9798     int oob[2];
9799     int i;
9800     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9801
9802     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9803     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9804
9805     *buf = '\0';
9806
9807     if( oob[0] >= 0 || oob[1] >= 0 ) {
9808         for( i=0; i<2; i++ ) {
9809             int idx = oob[i];
9810
9811             if( idx >= 0 ) {
9812                 if( i > 0 && oob[0] >= 0 ) {
9813                     strcat( buf, "   " );
9814                 }
9815
9816                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9817                 sprintf( buf+strlen(buf), "%s%.2f", 
9818                     pvInfoList[idx].score >= 0 ? "+" : "",
9819                     pvInfoList[idx].score / 100.0 );
9820             }
9821         }
9822     }
9823 }
9824
9825 /* Save game in PGN style and close the file */
9826 int
9827 SaveGamePGN(f)
9828      FILE *f;
9829 {
9830     int i, offset, linelen, newblock;
9831     time_t tm;
9832 //    char *movetext;
9833     char numtext[32];
9834     int movelen, numlen, blank;
9835     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9836
9837     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9838     
9839     tm = time((time_t *) NULL);
9840     
9841     PrintPGNTags(f, &gameInfo);
9842     
9843     if (backwardMostMove > 0 || startedFromSetupPosition) {
9844         char *fen = PositionToFEN(backwardMostMove, NULL);
9845         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9846         fprintf(f, "\n{--------------\n");
9847         PrintPosition(f, backwardMostMove);
9848         fprintf(f, "--------------}\n");
9849         free(fen);
9850     }
9851     else {
9852         /* [AS] Out of book annotation */
9853         if( appData.saveOutOfBookInfo ) {
9854             char buf[64];
9855
9856             GetOutOfBookInfo( buf );
9857
9858             if( buf[0] != '\0' ) {
9859                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9860             }
9861         }
9862
9863         fprintf(f, "\n");
9864     }
9865
9866     i = backwardMostMove;
9867     linelen = 0;
9868     newblock = TRUE;
9869
9870     while (i < forwardMostMove) {
9871         /* Print comments preceding this move */
9872         if (commentList[i] != NULL) {
9873             if (linelen > 0) fprintf(f, "\n");
9874             fprintf(f, "%s", commentList[i]);
9875             linelen = 0;
9876             newblock = TRUE;
9877         }
9878
9879         /* Format move number */
9880         if ((i % 2) == 0) {
9881             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9882         } else {
9883             if (newblock) {
9884                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9885             } else {
9886                 numtext[0] = NULLCHAR;
9887             }
9888         }
9889         numlen = strlen(numtext);
9890         newblock = FALSE;
9891
9892         /* Print move number */
9893         blank = linelen > 0 && numlen > 0;
9894         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9895             fprintf(f, "\n");
9896             linelen = 0;
9897             blank = 0;
9898         }
9899         if (blank) {
9900             fprintf(f, " ");
9901             linelen++;
9902         }
9903         fprintf(f, "%s", numtext);
9904         linelen += numlen;
9905
9906         /* Get move */
9907         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9908         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9909
9910         /* Print move */
9911         blank = linelen > 0 && movelen > 0;
9912         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9913             fprintf(f, "\n");
9914             linelen = 0;
9915             blank = 0;
9916         }
9917         if (blank) {
9918             fprintf(f, " ");
9919             linelen++;
9920         }
9921         fprintf(f, "%s", move_buffer);
9922         linelen += movelen;
9923
9924         /* [AS] Add PV info if present */
9925         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9926             /* [HGM] add time */
9927             char buf[MSG_SIZ]; int seconds;
9928
9929             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9930
9931             if( seconds <= 0) buf[0] = 0; else
9932             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9933                 seconds = (seconds + 4)/10; // round to full seconds
9934                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9935                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9936             }
9937
9938             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9939                 pvInfoList[i].score >= 0 ? "+" : "",
9940                 pvInfoList[i].score / 100.0,
9941                 pvInfoList[i].depth,
9942                 buf );
9943
9944             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9945
9946             /* Print score/depth */
9947             blank = linelen > 0 && movelen > 0;
9948             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9949                 fprintf(f, "\n");
9950                 linelen = 0;
9951                 blank = 0;
9952             }
9953             if (blank) {
9954                 fprintf(f, " ");
9955                 linelen++;
9956             }
9957             fprintf(f, "%s", move_buffer);
9958             linelen += movelen;
9959         }
9960
9961         i++;
9962     }
9963     
9964     /* Start a new line */
9965     if (linelen > 0) fprintf(f, "\n");
9966
9967     /* Print comments after last move */
9968     if (commentList[i] != NULL) {
9969         fprintf(f, "%s\n", commentList[i]);
9970     }
9971
9972     /* Print result */
9973     if (gameInfo.resultDetails != NULL &&
9974         gameInfo.resultDetails[0] != NULLCHAR) {
9975         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9976                 PGNResult(gameInfo.result));
9977     } else {
9978         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9979     }
9980
9981     fclose(f);
9982     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9983     return TRUE;
9984 }
9985
9986 /* Save game in old style and close the file */
9987 int
9988 SaveGameOldStyle(f)
9989      FILE *f;
9990 {
9991     int i, offset;
9992     time_t tm;
9993     
9994     tm = time((time_t *) NULL);
9995     
9996     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9997     PrintOpponents(f);
9998     
9999     if (backwardMostMove > 0 || startedFromSetupPosition) {
10000         fprintf(f, "\n[--------------\n");
10001         PrintPosition(f, backwardMostMove);
10002         fprintf(f, "--------------]\n");
10003     } else {
10004         fprintf(f, "\n");
10005     }
10006
10007     i = backwardMostMove;
10008     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10009
10010     while (i < forwardMostMove) {
10011         if (commentList[i] != NULL) {
10012             fprintf(f, "[%s]\n", commentList[i]);
10013         }
10014
10015         if ((i % 2) == 1) {
10016             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10017             i++;
10018         } else {
10019             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10020             i++;
10021             if (commentList[i] != NULL) {
10022                 fprintf(f, "\n");
10023                 continue;
10024             }
10025             if (i >= forwardMostMove) {
10026                 fprintf(f, "\n");
10027                 break;
10028             }
10029             fprintf(f, "%s\n", parseList[i]);
10030             i++;
10031         }
10032     }
10033     
10034     if (commentList[i] != NULL) {
10035         fprintf(f, "[%s]\n", commentList[i]);
10036     }
10037
10038     /* This isn't really the old style, but it's close enough */
10039     if (gameInfo.resultDetails != NULL &&
10040         gameInfo.resultDetails[0] != NULLCHAR) {
10041         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10042                 gameInfo.resultDetails);
10043     } else {
10044         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10045     }
10046
10047     fclose(f);
10048     return TRUE;
10049 }
10050
10051 /* Save the current game to open file f and close the file */
10052 int
10053 SaveGame(f, dummy, dummy2)
10054      FILE *f;
10055      int dummy;
10056      char *dummy2;
10057 {
10058     if (gameMode == EditPosition) EditPositionDone(TRUE);
10059     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10060     if (appData.oldSaveStyle)
10061       return SaveGameOldStyle(f);
10062     else
10063       return SaveGamePGN(f);
10064 }
10065
10066 /* Save the current position to the given file */
10067 int
10068 SavePositionToFile(filename)
10069      char *filename;
10070 {
10071     FILE *f;
10072     char buf[MSG_SIZ];
10073
10074     if (strcmp(filename, "-") == 0) {
10075         return SavePosition(stdout, 0, NULL);
10076     } else {
10077         f = fopen(filename, "a");
10078         if (f == NULL) {
10079             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10080             DisplayError(buf, errno);
10081             return FALSE;
10082         } else {
10083             SavePosition(f, 0, NULL);
10084             return TRUE;
10085         }
10086     }
10087 }
10088
10089 /* Save the current position to the given open file and close the file */
10090 int
10091 SavePosition(f, dummy, dummy2)
10092      FILE *f;
10093      int dummy;
10094      char *dummy2;
10095 {
10096     time_t tm;
10097     char *fen;
10098     
10099     if (gameMode == EditPosition) EditPositionDone(TRUE);
10100     if (appData.oldSaveStyle) {
10101         tm = time((time_t *) NULL);
10102     
10103         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10104         PrintOpponents(f);
10105         fprintf(f, "[--------------\n");
10106         PrintPosition(f, currentMove);
10107         fprintf(f, "--------------]\n");
10108     } else {
10109         fen = PositionToFEN(currentMove, NULL);
10110         fprintf(f, "%s\n", fen);
10111         free(fen);
10112     }
10113     fclose(f);
10114     return TRUE;
10115 }
10116
10117 void
10118 ReloadCmailMsgEvent(unregister)
10119      int unregister;
10120 {
10121 #if !WIN32
10122     static char *inFilename = NULL;
10123     static char *outFilename;
10124     int i;
10125     struct stat inbuf, outbuf;
10126     int status;
10127     
10128     /* Any registered moves are unregistered if unregister is set, */
10129     /* i.e. invoked by the signal handler */
10130     if (unregister) {
10131         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10132             cmailMoveRegistered[i] = FALSE;
10133             if (cmailCommentList[i] != NULL) {
10134                 free(cmailCommentList[i]);
10135                 cmailCommentList[i] = NULL;
10136             }
10137         }
10138         nCmailMovesRegistered = 0;
10139     }
10140
10141     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10142         cmailResult[i] = CMAIL_NOT_RESULT;
10143     }
10144     nCmailResults = 0;
10145
10146     if (inFilename == NULL) {
10147         /* Because the filenames are static they only get malloced once  */
10148         /* and they never get freed                                      */
10149         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10150         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10151
10152         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10153         sprintf(outFilename, "%s.out", appData.cmailGameName);
10154     }
10155     
10156     status = stat(outFilename, &outbuf);
10157     if (status < 0) {
10158         cmailMailedMove = FALSE;
10159     } else {
10160         status = stat(inFilename, &inbuf);
10161         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10162     }
10163     
10164     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10165        counts the games, notes how each one terminated, etc.
10166        
10167        It would be nice to remove this kludge and instead gather all
10168        the information while building the game list.  (And to keep it
10169        in the game list nodes instead of having a bunch of fixed-size
10170        parallel arrays.)  Note this will require getting each game's
10171        termination from the PGN tags, as the game list builder does
10172        not process the game moves.  --mann
10173        */
10174     cmailMsgLoaded = TRUE;
10175     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10176     
10177     /* Load first game in the file or popup game menu */
10178     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10179
10180 #endif /* !WIN32 */
10181     return;
10182 }
10183
10184 int
10185 RegisterMove()
10186 {
10187     FILE *f;
10188     char string[MSG_SIZ];
10189
10190     if (   cmailMailedMove
10191         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10192         return TRUE;            /* Allow free viewing  */
10193     }
10194
10195     /* Unregister move to ensure that we don't leave RegisterMove        */
10196     /* with the move registered when the conditions for registering no   */
10197     /* longer hold                                                       */
10198     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10199         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10200         nCmailMovesRegistered --;
10201
10202         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10203           {
10204               free(cmailCommentList[lastLoadGameNumber - 1]);
10205               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10206           }
10207     }
10208
10209     if (cmailOldMove == -1) {
10210         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10211         return FALSE;
10212     }
10213
10214     if (currentMove > cmailOldMove + 1) {
10215         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10216         return FALSE;
10217     }
10218
10219     if (currentMove < cmailOldMove) {
10220         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10221         return FALSE;
10222     }
10223
10224     if (forwardMostMove > currentMove) {
10225         /* Silently truncate extra moves */
10226         TruncateGame();
10227     }
10228
10229     if (   (currentMove == cmailOldMove + 1)
10230         || (   (currentMove == cmailOldMove)
10231             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10232                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10233         if (gameInfo.result != GameUnfinished) {
10234             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10235         }
10236
10237         if (commentList[currentMove] != NULL) {
10238             cmailCommentList[lastLoadGameNumber - 1]
10239               = StrSave(commentList[currentMove]);
10240         }
10241         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10242
10243         if (appData.debugMode)
10244           fprintf(debugFP, "Saving %s for game %d\n",
10245                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10246
10247         sprintf(string,
10248                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10249         
10250         f = fopen(string, "w");
10251         if (appData.oldSaveStyle) {
10252             SaveGameOldStyle(f); /* also closes the file */
10253             
10254             sprintf(string, "%s.pos.out", appData.cmailGameName);
10255             f = fopen(string, "w");
10256             SavePosition(f, 0, NULL); /* also closes the file */
10257         } else {
10258             fprintf(f, "{--------------\n");
10259             PrintPosition(f, currentMove);
10260             fprintf(f, "--------------}\n\n");
10261             
10262             SaveGame(f, 0, NULL); /* also closes the file*/
10263         }
10264         
10265         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10266         nCmailMovesRegistered ++;
10267     } else if (nCmailGames == 1) {
10268         DisplayError(_("You have not made a move yet"), 0);
10269         return FALSE;
10270     }
10271
10272     return TRUE;
10273 }
10274
10275 void
10276 MailMoveEvent()
10277 {
10278 #if !WIN32
10279     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10280     FILE *commandOutput;
10281     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10282     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10283     int nBuffers;
10284     int i;
10285     int archived;
10286     char *arcDir;
10287
10288     if (! cmailMsgLoaded) {
10289         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10290         return;
10291     }
10292
10293     if (nCmailGames == nCmailResults) {
10294         DisplayError(_("No unfinished games"), 0);
10295         return;
10296     }
10297
10298 #if CMAIL_PROHIBIT_REMAIL
10299     if (cmailMailedMove) {
10300         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);
10301         DisplayError(msg, 0);
10302         return;
10303     }
10304 #endif
10305
10306     if (! (cmailMailedMove || RegisterMove())) return;
10307     
10308     if (   cmailMailedMove
10309         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10310         sprintf(string, partCommandString,
10311                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10312         commandOutput = popen(string, "r");
10313
10314         if (commandOutput == NULL) {
10315             DisplayError(_("Failed to invoke cmail"), 0);
10316         } else {
10317             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10318                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10319             }
10320             if (nBuffers > 1) {
10321                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10322                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10323                 nBytes = MSG_SIZ - 1;
10324             } else {
10325                 (void) memcpy(msg, buffer, nBytes);
10326             }
10327             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10328
10329             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10330                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10331
10332                 archived = TRUE;
10333                 for (i = 0; i < nCmailGames; i ++) {
10334                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10335                         archived = FALSE;
10336                     }
10337                 }
10338                 if (   archived
10339                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10340                         != NULL)) {
10341                     sprintf(buffer, "%s/%s.%s.archive",
10342                             arcDir,
10343                             appData.cmailGameName,
10344                             gameInfo.date);
10345                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10346                     cmailMsgLoaded = FALSE;
10347                 }
10348             }
10349
10350             DisplayInformation(msg);
10351             pclose(commandOutput);
10352         }
10353     } else {
10354         if ((*cmailMsg) != '\0') {
10355             DisplayInformation(cmailMsg);
10356         }
10357     }
10358
10359     return;
10360 #endif /* !WIN32 */
10361 }
10362
10363 char *
10364 CmailMsg()
10365 {
10366 #if WIN32
10367     return NULL;
10368 #else
10369     int  prependComma = 0;
10370     char number[5];
10371     char string[MSG_SIZ];       /* Space for game-list */
10372     int  i;
10373     
10374     if (!cmailMsgLoaded) return "";
10375
10376     if (cmailMailedMove) {
10377         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10378     } else {
10379         /* Create a list of games left */
10380         sprintf(string, "[");
10381         for (i = 0; i < nCmailGames; i ++) {
10382             if (! (   cmailMoveRegistered[i]
10383                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10384                 if (prependComma) {
10385                     sprintf(number, ",%d", i + 1);
10386                 } else {
10387                     sprintf(number, "%d", i + 1);
10388                     prependComma = 1;
10389                 }
10390                 
10391                 strcat(string, number);
10392             }
10393         }
10394         strcat(string, "]");
10395
10396         if (nCmailMovesRegistered + nCmailResults == 0) {
10397             switch (nCmailGames) {
10398               case 1:
10399                 sprintf(cmailMsg,
10400                         _("Still need to make move for game\n"));
10401                 break;
10402                 
10403               case 2:
10404                 sprintf(cmailMsg,
10405                         _("Still need to make moves for both games\n"));
10406                 break;
10407                 
10408               default:
10409                 sprintf(cmailMsg,
10410                         _("Still need to make moves for all %d games\n"),
10411                         nCmailGames);
10412                 break;
10413             }
10414         } else {
10415             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10416               case 1:
10417                 sprintf(cmailMsg,
10418                         _("Still need to make a move for game %s\n"),
10419                         string);
10420                 break;
10421                 
10422               case 0:
10423                 if (nCmailResults == nCmailGames) {
10424                     sprintf(cmailMsg, _("No unfinished games\n"));
10425                 } else {
10426                     sprintf(cmailMsg, _("Ready to send mail\n"));
10427                 }
10428                 break;
10429                 
10430               default:
10431                 sprintf(cmailMsg,
10432                         _("Still need to make moves for games %s\n"),
10433                         string);
10434             }
10435         }
10436     }
10437     return cmailMsg;
10438 #endif /* WIN32 */
10439 }
10440
10441 void
10442 ResetGameEvent()
10443 {
10444     if (gameMode == Training)
10445       SetTrainingModeOff();
10446
10447     Reset(TRUE, TRUE);
10448     cmailMsgLoaded = FALSE;
10449     if (appData.icsActive) {
10450       SendToICS(ics_prefix);
10451       SendToICS("refresh\n");
10452     }
10453 }
10454
10455 void
10456 ExitEvent(status)
10457      int status;
10458 {
10459     exiting++;
10460     if (exiting > 2) {
10461       /* Give up on clean exit */
10462       exit(status);
10463     }
10464     if (exiting > 1) {
10465       /* Keep trying for clean exit */
10466       return;
10467     }
10468
10469     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10470
10471     if (telnetISR != NULL) {
10472       RemoveInputSource(telnetISR);
10473     }
10474     if (icsPR != NoProc) {
10475       DestroyChildProcess(icsPR, TRUE);
10476     }
10477
10478     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10479     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10480
10481     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10482     /* make sure this other one finishes before killing it!                  */
10483     if(endingGame) { int count = 0;
10484         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10485         while(endingGame && count++ < 10) DoSleep(1);
10486         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10487     }
10488
10489     /* Kill off chess programs */
10490     if (first.pr != NoProc) {
10491         ExitAnalyzeMode();
10492         
10493         DoSleep( appData.delayBeforeQuit );
10494         SendToProgram("quit\n", &first);
10495         DoSleep( appData.delayAfterQuit );
10496         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10497     }
10498     if (second.pr != NoProc) {
10499         DoSleep( appData.delayBeforeQuit );
10500         SendToProgram("quit\n", &second);
10501         DoSleep( appData.delayAfterQuit );
10502         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10503     }
10504     if (first.isr != NULL) {
10505         RemoveInputSource(first.isr);
10506     }
10507     if (second.isr != NULL) {
10508         RemoveInputSource(second.isr);
10509     }
10510
10511     ShutDownFrontEnd();
10512     exit(status);
10513 }
10514
10515 void
10516 PauseEvent()
10517 {
10518     if (appData.debugMode)
10519         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10520     if (pausing) {
10521         pausing = FALSE;
10522         ModeHighlight();
10523         if (gameMode == MachinePlaysWhite ||
10524             gameMode == MachinePlaysBlack) {
10525             StartClocks();
10526         } else {
10527             DisplayBothClocks();
10528         }
10529         if (gameMode == PlayFromGameFile) {
10530             if (appData.timeDelay >= 0) 
10531                 AutoPlayGameLoop();
10532         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10533             Reset(FALSE, TRUE);
10534             SendToICS(ics_prefix);
10535             SendToICS("refresh\n");
10536         } else if (currentMove < forwardMostMove) {
10537             ForwardInner(forwardMostMove);
10538         }
10539         pauseExamInvalid = FALSE;
10540     } else {
10541         switch (gameMode) {
10542           default:
10543             return;
10544           case IcsExamining:
10545             pauseExamForwardMostMove = forwardMostMove;
10546             pauseExamInvalid = FALSE;
10547             /* fall through */
10548           case IcsObserving:
10549           case IcsPlayingWhite:
10550           case IcsPlayingBlack:
10551             pausing = TRUE;
10552             ModeHighlight();
10553             return;
10554           case PlayFromGameFile:
10555             (void) StopLoadGameTimer();
10556             pausing = TRUE;
10557             ModeHighlight();
10558             break;
10559           case BeginningOfGame:
10560             if (appData.icsActive) return;
10561             /* else fall through */
10562           case MachinePlaysWhite:
10563           case MachinePlaysBlack:
10564           case TwoMachinesPlay:
10565             if (forwardMostMove == 0)
10566               return;           /* don't pause if no one has moved */
10567             if ((gameMode == MachinePlaysWhite &&
10568                  !WhiteOnMove(forwardMostMove)) ||
10569                 (gameMode == MachinePlaysBlack &&
10570                  WhiteOnMove(forwardMostMove))) {
10571                 StopClocks();
10572             }
10573             pausing = TRUE;
10574             ModeHighlight();
10575             break;
10576         }
10577     }
10578 }
10579
10580 void
10581 EditCommentEvent()
10582 {
10583     char title[MSG_SIZ];
10584
10585     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10586         strcpy(title, _("Edit comment"));
10587     } else {
10588         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10589                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10590                 parseList[currentMove - 1]);
10591     }
10592
10593     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10594 }
10595
10596
10597 void
10598 EditTagsEvent()
10599 {
10600     char *tags = PGNTags(&gameInfo);
10601     EditTagsPopUp(tags);
10602     free(tags);
10603 }
10604
10605 void
10606 AnalyzeModeEvent()
10607 {
10608     if (appData.noChessProgram || gameMode == AnalyzeMode)
10609       return;
10610
10611     if (gameMode != AnalyzeFile) {
10612         if (!appData.icsEngineAnalyze) {
10613                EditGameEvent();
10614                if (gameMode != EditGame) return;
10615         }
10616         ResurrectChessProgram();
10617         SendToProgram("analyze\n", &first);
10618         first.analyzing = TRUE;
10619         /*first.maybeThinking = TRUE;*/
10620         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10621         EngineOutputPopUp();
10622     }
10623     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10624     pausing = FALSE;
10625     ModeHighlight();
10626     SetGameInfo();
10627
10628     StartAnalysisClock();
10629     GetTimeMark(&lastNodeCountTime);
10630     lastNodeCount = 0;
10631 }
10632
10633 void
10634 AnalyzeFileEvent()
10635 {
10636     if (appData.noChessProgram || gameMode == AnalyzeFile)
10637       return;
10638
10639     if (gameMode != AnalyzeMode) {
10640         EditGameEvent();
10641         if (gameMode != EditGame) return;
10642         ResurrectChessProgram();
10643         SendToProgram("analyze\n", &first);
10644         first.analyzing = TRUE;
10645         /*first.maybeThinking = TRUE;*/
10646         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10647         EngineOutputPopUp();
10648     }
10649     gameMode = AnalyzeFile;
10650     pausing = FALSE;
10651     ModeHighlight();
10652     SetGameInfo();
10653
10654     StartAnalysisClock();
10655     GetTimeMark(&lastNodeCountTime);
10656     lastNodeCount = 0;
10657 }
10658
10659 void
10660 MachineWhiteEvent()
10661 {
10662     char buf[MSG_SIZ];
10663     char *bookHit = NULL;
10664
10665     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10666       return;
10667
10668
10669     if (gameMode == PlayFromGameFile || 
10670         gameMode == TwoMachinesPlay  || 
10671         gameMode == Training         || 
10672         gameMode == AnalyzeMode      || 
10673         gameMode == EndOfGame)
10674         EditGameEvent();
10675
10676     if (gameMode == EditPosition) 
10677         EditPositionDone(TRUE);
10678
10679     if (!WhiteOnMove(currentMove)) {
10680         DisplayError(_("It is not White's turn"), 0);
10681         return;
10682     }
10683   
10684     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10685       ExitAnalyzeMode();
10686
10687     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10688         gameMode == AnalyzeFile)
10689         TruncateGame();
10690
10691     ResurrectChessProgram();    /* in case it isn't running */
10692     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10693         gameMode = MachinePlaysWhite;
10694         ResetClocks();
10695     } else
10696     gameMode = MachinePlaysWhite;
10697     pausing = FALSE;
10698     ModeHighlight();
10699     SetGameInfo();
10700     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10701     DisplayTitle(buf);
10702     if (first.sendName) {
10703       sprintf(buf, "name %s\n", gameInfo.black);
10704       SendToProgram(buf, &first);
10705     }
10706     if (first.sendTime) {
10707       if (first.useColors) {
10708         SendToProgram("black\n", &first); /*gnu kludge*/
10709       }
10710       SendTimeRemaining(&first, TRUE);
10711     }
10712     if (first.useColors) {
10713       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10714     }
10715     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10716     SetMachineThinkingEnables();
10717     first.maybeThinking = TRUE;
10718     StartClocks();
10719     firstMove = FALSE;
10720
10721     if (appData.autoFlipView && !flipView) {
10722       flipView = !flipView;
10723       DrawPosition(FALSE, NULL);
10724       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10725     }
10726
10727     if(bookHit) { // [HGM] book: simulate book reply
10728         static char bookMove[MSG_SIZ]; // a bit generous?
10729
10730         programStats.nodes = programStats.depth = programStats.time = 
10731         programStats.score = programStats.got_only_move = 0;
10732         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10733
10734         strcpy(bookMove, "move ");
10735         strcat(bookMove, bookHit);
10736         HandleMachineMove(bookMove, &first);
10737     }
10738 }
10739
10740 void
10741 MachineBlackEvent()
10742 {
10743     char buf[MSG_SIZ];
10744    char *bookHit = NULL;
10745
10746     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10747         return;
10748
10749
10750     if (gameMode == PlayFromGameFile || 
10751         gameMode == TwoMachinesPlay  || 
10752         gameMode == Training         || 
10753         gameMode == AnalyzeMode      || 
10754         gameMode == EndOfGame)
10755         EditGameEvent();
10756
10757     if (gameMode == EditPosition) 
10758         EditPositionDone(TRUE);
10759
10760     if (WhiteOnMove(currentMove)) {
10761         DisplayError(_("It is not Black's turn"), 0);
10762         return;
10763     }
10764     
10765     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10766       ExitAnalyzeMode();
10767
10768     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10769         gameMode == AnalyzeFile)
10770         TruncateGame();
10771
10772     ResurrectChessProgram();    /* in case it isn't running */
10773     gameMode = MachinePlaysBlack;
10774     pausing = FALSE;
10775     ModeHighlight();
10776     SetGameInfo();
10777     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10778     DisplayTitle(buf);
10779     if (first.sendName) {
10780       sprintf(buf, "name %s\n", gameInfo.white);
10781       SendToProgram(buf, &first);
10782     }
10783     if (first.sendTime) {
10784       if (first.useColors) {
10785         SendToProgram("white\n", &first); /*gnu kludge*/
10786       }
10787       SendTimeRemaining(&first, FALSE);
10788     }
10789     if (first.useColors) {
10790       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10791     }
10792     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10793     SetMachineThinkingEnables();
10794     first.maybeThinking = TRUE;
10795     StartClocks();
10796
10797     if (appData.autoFlipView && flipView) {
10798       flipView = !flipView;
10799       DrawPosition(FALSE, NULL);
10800       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10801     }
10802     if(bookHit) { // [HGM] book: simulate book reply
10803         static char bookMove[MSG_SIZ]; // a bit generous?
10804
10805         programStats.nodes = programStats.depth = programStats.time = 
10806         programStats.score = programStats.got_only_move = 0;
10807         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10808
10809         strcpy(bookMove, "move ");
10810         strcat(bookMove, bookHit);
10811         HandleMachineMove(bookMove, &first);
10812     }
10813 }
10814
10815
10816 void
10817 DisplayTwoMachinesTitle()
10818 {
10819     char buf[MSG_SIZ];
10820     if (appData.matchGames > 0) {
10821         if (first.twoMachinesColor[0] == 'w') {
10822             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10823                     gameInfo.white, gameInfo.black,
10824                     first.matchWins, second.matchWins,
10825                     matchGame - 1 - (first.matchWins + second.matchWins));
10826         } else {
10827             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10828                     gameInfo.white, gameInfo.black,
10829                     second.matchWins, first.matchWins,
10830                     matchGame - 1 - (first.matchWins + second.matchWins));
10831         }
10832     } else {
10833         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10834     }
10835     DisplayTitle(buf);
10836 }
10837
10838 void
10839 TwoMachinesEvent P((void))
10840 {
10841     int i;
10842     char buf[MSG_SIZ];
10843     ChessProgramState *onmove;
10844     char *bookHit = NULL;
10845     
10846     if (appData.noChessProgram) return;
10847
10848     switch (gameMode) {
10849       case TwoMachinesPlay:
10850         return;
10851       case MachinePlaysWhite:
10852       case MachinePlaysBlack:
10853         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10854             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10855             return;
10856         }
10857         /* fall through */
10858       case BeginningOfGame:
10859       case PlayFromGameFile:
10860       case EndOfGame:
10861         EditGameEvent();
10862         if (gameMode != EditGame) return;
10863         break;
10864       case EditPosition:
10865         EditPositionDone(TRUE);
10866         break;
10867       case AnalyzeMode:
10868       case AnalyzeFile:
10869         ExitAnalyzeMode();
10870         break;
10871       case EditGame:
10872       default:
10873         break;
10874     }
10875
10876 //    forwardMostMove = currentMove;
10877     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10878     ResurrectChessProgram();    /* in case first program isn't running */
10879
10880     if (second.pr == NULL) {
10881         StartChessProgram(&second);
10882         if (second.protocolVersion == 1) {
10883           TwoMachinesEventIfReady();
10884         } else {
10885           /* kludge: allow timeout for initial "feature" command */
10886           FreezeUI();
10887           DisplayMessage("", _("Starting second chess program"));
10888           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10889         }
10890         return;
10891     }
10892     DisplayMessage("", "");
10893     InitChessProgram(&second, FALSE);
10894     SendToProgram("force\n", &second);
10895     if (startedFromSetupPosition) {
10896         SendBoard(&second, backwardMostMove);
10897     if (appData.debugMode) {
10898         fprintf(debugFP, "Two Machines\n");
10899     }
10900     }
10901     for (i = backwardMostMove; i < forwardMostMove; i++) {
10902         SendMoveToProgram(i, &second);
10903     }
10904
10905     gameMode = TwoMachinesPlay;
10906     pausing = FALSE;
10907     ModeHighlight();
10908     SetGameInfo();
10909     DisplayTwoMachinesTitle();
10910     firstMove = TRUE;
10911     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10912         onmove = &first;
10913     } else {
10914         onmove = &second;
10915     }
10916
10917     SendToProgram(first.computerString, &first);
10918     if (first.sendName) {
10919       sprintf(buf, "name %s\n", second.tidy);
10920       SendToProgram(buf, &first);
10921     }
10922     SendToProgram(second.computerString, &second);
10923     if (second.sendName) {
10924       sprintf(buf, "name %s\n", first.tidy);
10925       SendToProgram(buf, &second);
10926     }
10927
10928     ResetClocks();
10929     if (!first.sendTime || !second.sendTime) {
10930         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10931         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10932     }
10933     if (onmove->sendTime) {
10934       if (onmove->useColors) {
10935         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10936       }
10937       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10938     }
10939     if (onmove->useColors) {
10940       SendToProgram(onmove->twoMachinesColor, onmove);
10941     }
10942     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10943 //    SendToProgram("go\n", onmove);
10944     onmove->maybeThinking = TRUE;
10945     SetMachineThinkingEnables();
10946
10947     StartClocks();
10948
10949     if(bookHit) { // [HGM] book: simulate book reply
10950         static char bookMove[MSG_SIZ]; // a bit generous?
10951
10952         programStats.nodes = programStats.depth = programStats.time = 
10953         programStats.score = programStats.got_only_move = 0;
10954         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10955
10956         strcpy(bookMove, "move ");
10957         strcat(bookMove, bookHit);
10958         savedMessage = bookMove; // args for deferred call
10959         savedState = onmove;
10960         ScheduleDelayedEvent(DeferredBookMove, 1);
10961     }
10962 }
10963
10964 void
10965 TrainingEvent()
10966 {
10967     if (gameMode == Training) {
10968       SetTrainingModeOff();
10969       gameMode = PlayFromGameFile;
10970       DisplayMessage("", _("Training mode off"));
10971     } else {
10972       gameMode = Training;
10973       animateTraining = appData.animate;
10974
10975       /* make sure we are not already at the end of the game */
10976       if (currentMove < forwardMostMove) {
10977         SetTrainingModeOn();
10978         DisplayMessage("", _("Training mode on"));
10979       } else {
10980         gameMode = PlayFromGameFile;
10981         DisplayError(_("Already at end of game"), 0);
10982       }
10983     }
10984     ModeHighlight();
10985 }
10986
10987 void
10988 IcsClientEvent()
10989 {
10990     if (!appData.icsActive) return;
10991     switch (gameMode) {
10992       case IcsPlayingWhite:
10993       case IcsPlayingBlack:
10994       case IcsObserving:
10995       case IcsIdle:
10996       case BeginningOfGame:
10997       case IcsExamining:
10998         return;
10999
11000       case EditGame:
11001         break;
11002
11003       case EditPosition:
11004         EditPositionDone(TRUE);
11005         break;
11006
11007       case AnalyzeMode:
11008       case AnalyzeFile:
11009         ExitAnalyzeMode();
11010         break;
11011         
11012       default:
11013         EditGameEvent();
11014         break;
11015     }
11016
11017     gameMode = IcsIdle;
11018     ModeHighlight();
11019     return;
11020 }
11021
11022
11023 void
11024 EditGameEvent()
11025 {
11026     int i;
11027
11028     switch (gameMode) {
11029       case Training:
11030         SetTrainingModeOff();
11031         break;
11032       case MachinePlaysWhite:
11033       case MachinePlaysBlack:
11034       case BeginningOfGame:
11035         SendToProgram("force\n", &first);
11036         SetUserThinkingEnables();
11037         break;
11038       case PlayFromGameFile:
11039         (void) StopLoadGameTimer();
11040         if (gameFileFP != NULL) {
11041             gameFileFP = NULL;
11042         }
11043         break;
11044       case EditPosition:
11045         EditPositionDone(TRUE);
11046         break;
11047       case AnalyzeMode:
11048       case AnalyzeFile:
11049         ExitAnalyzeMode();
11050         SendToProgram("force\n", &first);
11051         break;
11052       case TwoMachinesPlay:
11053         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11054         ResurrectChessProgram();
11055         SetUserThinkingEnables();
11056         break;
11057       case EndOfGame:
11058         ResurrectChessProgram();
11059         break;
11060       case IcsPlayingBlack:
11061       case IcsPlayingWhite:
11062         DisplayError(_("Warning: You are still playing a game"), 0);
11063         break;
11064       case IcsObserving:
11065         DisplayError(_("Warning: You are still observing a game"), 0);
11066         break;
11067       case IcsExamining:
11068         DisplayError(_("Warning: You are still examining a game"), 0);
11069         break;
11070       case IcsIdle:
11071         break;
11072       case EditGame:
11073       default:
11074         return;
11075     }
11076     
11077     pausing = FALSE;
11078     StopClocks();
11079     first.offeredDraw = second.offeredDraw = 0;
11080
11081     if (gameMode == PlayFromGameFile) {
11082         whiteTimeRemaining = timeRemaining[0][currentMove];
11083         blackTimeRemaining = timeRemaining[1][currentMove];
11084         DisplayTitle("");
11085     }
11086
11087     if (gameMode == MachinePlaysWhite ||
11088         gameMode == MachinePlaysBlack ||
11089         gameMode == TwoMachinesPlay ||
11090         gameMode == EndOfGame) {
11091         i = forwardMostMove;
11092         while (i > currentMove) {
11093             SendToProgram("undo\n", &first);
11094             i--;
11095         }
11096         whiteTimeRemaining = timeRemaining[0][currentMove];
11097         blackTimeRemaining = timeRemaining[1][currentMove];
11098         DisplayBothClocks();
11099         if (whiteFlag || blackFlag) {
11100             whiteFlag = blackFlag = 0;
11101         }
11102         DisplayTitle("");
11103     }           
11104     
11105     gameMode = EditGame;
11106     ModeHighlight();
11107     SetGameInfo();
11108 }
11109
11110
11111 void
11112 EditPositionEvent()
11113 {
11114     if (gameMode == EditPosition) {
11115         EditGameEvent();
11116         return;
11117     }
11118     
11119     EditGameEvent();
11120     if (gameMode != EditGame) return;
11121     
11122     gameMode = EditPosition;
11123     ModeHighlight();
11124     SetGameInfo();
11125     if (currentMove > 0)
11126       CopyBoard(boards[0], boards[currentMove]);
11127     
11128     blackPlaysFirst = !WhiteOnMove(currentMove);
11129     ResetClocks();
11130     currentMove = forwardMostMove = backwardMostMove = 0;
11131     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11132     DisplayMove(-1);
11133 }
11134
11135 void
11136 ExitAnalyzeMode()
11137 {
11138     /* [DM] icsEngineAnalyze - possible call from other functions */
11139     if (appData.icsEngineAnalyze) {
11140         appData.icsEngineAnalyze = FALSE;
11141
11142         DisplayMessage("",_("Close ICS engine analyze..."));
11143     }
11144     if (first.analysisSupport && first.analyzing) {
11145       SendToProgram("exit\n", &first);
11146       first.analyzing = FALSE;
11147     }
11148     thinkOutput[0] = NULLCHAR;
11149 }
11150
11151 void
11152 EditPositionDone(Boolean fakeRights)
11153 {
11154     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11155
11156     startedFromSetupPosition = TRUE;
11157     InitChessProgram(&first, FALSE);
11158     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11159       boards[0][EP_STATUS] = EP_NONE;
11160       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11161     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11162         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11163         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11164       } else boards[0][CASTLING][2] = NoRights;
11165     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11166         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11167         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11168       } else boards[0][CASTLING][5] = NoRights;
11169     }
11170     SendToProgram("force\n", &first);
11171     if (blackPlaysFirst) {
11172         strcpy(moveList[0], "");
11173         strcpy(parseList[0], "");
11174         currentMove = forwardMostMove = backwardMostMove = 1;
11175         CopyBoard(boards[1], boards[0]);
11176     } else {
11177         currentMove = forwardMostMove = backwardMostMove = 0;
11178     }
11179     SendBoard(&first, forwardMostMove);
11180     if (appData.debugMode) {
11181         fprintf(debugFP, "EditPosDone\n");
11182     }
11183     DisplayTitle("");
11184     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11185     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11186     gameMode = EditGame;
11187     ModeHighlight();
11188     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11189     ClearHighlights(); /* [AS] */
11190 }
11191
11192 /* Pause for `ms' milliseconds */
11193 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11194 void
11195 TimeDelay(ms)
11196      long ms;
11197 {
11198     TimeMark m1, m2;
11199
11200     GetTimeMark(&m1);
11201     do {
11202         GetTimeMark(&m2);
11203     } while (SubtractTimeMarks(&m2, &m1) < ms);
11204 }
11205
11206 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11207 void
11208 SendMultiLineToICS(buf)
11209      char *buf;
11210 {
11211     char temp[MSG_SIZ+1], *p;
11212     int len;
11213
11214     len = strlen(buf);
11215     if (len > MSG_SIZ)
11216       len = MSG_SIZ;
11217   
11218     strncpy(temp, buf, len);
11219     temp[len] = 0;
11220
11221     p = temp;
11222     while (*p) {
11223         if (*p == '\n' || *p == '\r')
11224           *p = ' ';
11225         ++p;
11226     }
11227
11228     strcat(temp, "\n");
11229     SendToICS(temp);
11230     SendToPlayer(temp, strlen(temp));
11231 }
11232
11233 void
11234 SetWhiteToPlayEvent()
11235 {
11236     if (gameMode == EditPosition) {
11237         blackPlaysFirst = FALSE;
11238         DisplayBothClocks();    /* works because currentMove is 0 */
11239     } else if (gameMode == IcsExamining) {
11240         SendToICS(ics_prefix);
11241         SendToICS("tomove white\n");
11242     }
11243 }
11244
11245 void
11246 SetBlackToPlayEvent()
11247 {
11248     if (gameMode == EditPosition) {
11249         blackPlaysFirst = TRUE;
11250         currentMove = 1;        /* kludge */
11251         DisplayBothClocks();
11252         currentMove = 0;
11253     } else if (gameMode == IcsExamining) {
11254         SendToICS(ics_prefix);
11255         SendToICS("tomove black\n");
11256     }
11257 }
11258
11259 void
11260 EditPositionMenuEvent(selection, x, y)
11261      ChessSquare selection;
11262      int x, y;
11263 {
11264     char buf[MSG_SIZ];
11265     ChessSquare piece = boards[0][y][x];
11266
11267     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11268
11269     switch (selection) {
11270       case ClearBoard:
11271         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11272             SendToICS(ics_prefix);
11273             SendToICS("bsetup clear\n");
11274         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11275             SendToICS(ics_prefix);
11276             SendToICS("clearboard\n");
11277         } else {
11278             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11279                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11280                 for (y = 0; y < BOARD_HEIGHT; y++) {
11281                     if (gameMode == IcsExamining) {
11282                         if (boards[currentMove][y][x] != EmptySquare) {
11283                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11284                                     AAA + x, ONE + y);
11285                             SendToICS(buf);
11286                         }
11287                     } else {
11288                         boards[0][y][x] = p;
11289                     }
11290                 }
11291             }
11292         }
11293         if (gameMode == EditPosition) {
11294             DrawPosition(FALSE, boards[0]);
11295         }
11296         break;
11297
11298       case WhitePlay:
11299         SetWhiteToPlayEvent();
11300         break;
11301
11302       case BlackPlay:
11303         SetBlackToPlayEvent();
11304         break;
11305
11306       case EmptySquare:
11307         if (gameMode == IcsExamining) {
11308             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11309             SendToICS(buf);
11310         } else {
11311             boards[0][y][x] = EmptySquare;
11312             DrawPosition(FALSE, boards[0]);
11313         }
11314         break;
11315
11316       case PromotePiece:
11317         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11318            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11319             selection = (ChessSquare) (PROMOTED piece);
11320         } else if(piece == EmptySquare) selection = WhiteSilver;
11321         else selection = (ChessSquare)((int)piece - 1);
11322         goto defaultlabel;
11323
11324       case DemotePiece:
11325         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11326            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11327             selection = (ChessSquare) (DEMOTED piece);
11328         } else if(piece == EmptySquare) selection = BlackSilver;
11329         else selection = (ChessSquare)((int)piece + 1);       
11330         goto defaultlabel;
11331
11332       case WhiteQueen:
11333       case BlackQueen:
11334         if(gameInfo.variant == VariantShatranj ||
11335            gameInfo.variant == VariantXiangqi  ||
11336            gameInfo.variant == VariantCourier    )
11337             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11338         goto defaultlabel;
11339
11340       case WhiteKing:
11341       case BlackKing:
11342         if(gameInfo.variant == VariantXiangqi)
11343             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11344         if(gameInfo.variant == VariantKnightmate)
11345             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11346       default:
11347         defaultlabel:
11348         if (gameMode == IcsExamining) {
11349             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11350                     PieceToChar(selection), AAA + x, ONE + y);
11351             SendToICS(buf);
11352         } else {
11353             boards[0][y][x] = selection;
11354             DrawPosition(FALSE, boards[0]);
11355         }
11356         break;
11357     }
11358 }
11359
11360
11361 void
11362 DropMenuEvent(selection, x, y)
11363      ChessSquare selection;
11364      int x, y;
11365 {
11366     ChessMove moveType;
11367
11368     switch (gameMode) {
11369       case IcsPlayingWhite:
11370       case MachinePlaysBlack:
11371         if (!WhiteOnMove(currentMove)) {
11372             DisplayMoveError(_("It is Black's turn"));
11373             return;
11374         }
11375         moveType = WhiteDrop;
11376         break;
11377       case IcsPlayingBlack:
11378       case MachinePlaysWhite:
11379         if (WhiteOnMove(currentMove)) {
11380             DisplayMoveError(_("It is White's turn"));
11381             return;
11382         }
11383         moveType = BlackDrop;
11384         break;
11385       case EditGame:
11386         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11387         break;
11388       default:
11389         return;
11390     }
11391
11392     if (moveType == BlackDrop && selection < BlackPawn) {
11393       selection = (ChessSquare) ((int) selection
11394                                  + (int) BlackPawn - (int) WhitePawn);
11395     }
11396     if (boards[currentMove][y][x] != EmptySquare) {
11397         DisplayMoveError(_("That square is occupied"));
11398         return;
11399     }
11400
11401     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11402 }
11403
11404 void
11405 AcceptEvent()
11406 {
11407     /* Accept a pending offer of any kind from opponent */
11408     
11409     if (appData.icsActive) {
11410         SendToICS(ics_prefix);
11411         SendToICS("accept\n");
11412     } else if (cmailMsgLoaded) {
11413         if (currentMove == cmailOldMove &&
11414             commentList[cmailOldMove] != NULL &&
11415             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11416                    "Black offers a draw" : "White offers a draw")) {
11417             TruncateGame();
11418             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11419             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11420         } else {
11421             DisplayError(_("There is no pending offer on this move"), 0);
11422             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11423         }
11424     } else {
11425         /* Not used for offers from chess program */
11426     }
11427 }
11428
11429 void
11430 DeclineEvent()
11431 {
11432     /* Decline a pending offer of any kind from opponent */
11433     
11434     if (appData.icsActive) {
11435         SendToICS(ics_prefix);
11436         SendToICS("decline\n");
11437     } else if (cmailMsgLoaded) {
11438         if (currentMove == cmailOldMove &&
11439             commentList[cmailOldMove] != NULL &&
11440             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11441                    "Black offers a draw" : "White offers a draw")) {
11442 #ifdef NOTDEF
11443             AppendComment(cmailOldMove, "Draw declined", TRUE);
11444             DisplayComment(cmailOldMove - 1, "Draw declined");
11445 #endif /*NOTDEF*/
11446         } else {
11447             DisplayError(_("There is no pending offer on this move"), 0);
11448         }
11449     } else {
11450         /* Not used for offers from chess program */
11451     }
11452 }
11453
11454 void
11455 RematchEvent()
11456 {
11457     /* Issue ICS rematch command */
11458     if (appData.icsActive) {
11459         SendToICS(ics_prefix);
11460         SendToICS("rematch\n");
11461     }
11462 }
11463
11464 void
11465 CallFlagEvent()
11466 {
11467     /* Call your opponent's flag (claim a win on time) */
11468     if (appData.icsActive) {
11469         SendToICS(ics_prefix);
11470         SendToICS("flag\n");
11471     } else {
11472         switch (gameMode) {
11473           default:
11474             return;
11475           case MachinePlaysWhite:
11476             if (whiteFlag) {
11477                 if (blackFlag)
11478                   GameEnds(GameIsDrawn, "Both players ran out of time",
11479                            GE_PLAYER);
11480                 else
11481                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11482             } else {
11483                 DisplayError(_("Your opponent is not out of time"), 0);
11484             }
11485             break;
11486           case MachinePlaysBlack:
11487             if (blackFlag) {
11488                 if (whiteFlag)
11489                   GameEnds(GameIsDrawn, "Both players ran out of time",
11490                            GE_PLAYER);
11491                 else
11492                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11493             } else {
11494                 DisplayError(_("Your opponent is not out of time"), 0);
11495             }
11496             break;
11497         }
11498     }
11499 }
11500
11501 void
11502 DrawEvent()
11503 {
11504     /* Offer draw or accept pending draw offer from opponent */
11505     
11506     if (appData.icsActive) {
11507         /* Note: tournament rules require draw offers to be
11508            made after you make your move but before you punch
11509            your clock.  Currently ICS doesn't let you do that;
11510            instead, you immediately punch your clock after making
11511            a move, but you can offer a draw at any time. */
11512         
11513         SendToICS(ics_prefix);
11514         SendToICS("draw\n");
11515     } else if (cmailMsgLoaded) {
11516         if (currentMove == cmailOldMove &&
11517             commentList[cmailOldMove] != NULL &&
11518             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11519                    "Black offers a draw" : "White offers a draw")) {
11520             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11521             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11522         } else if (currentMove == cmailOldMove + 1) {
11523             char *offer = WhiteOnMove(cmailOldMove) ?
11524               "White offers a draw" : "Black offers a draw";
11525             AppendComment(currentMove, offer, TRUE);
11526             DisplayComment(currentMove - 1, offer);
11527             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11528         } else {
11529             DisplayError(_("You must make your move before offering a draw"), 0);
11530             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11531         }
11532     } else if (first.offeredDraw) {
11533         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11534     } else {
11535         if (first.sendDrawOffers) {
11536             SendToProgram("draw\n", &first);
11537             userOfferedDraw = TRUE;
11538         }
11539     }
11540 }
11541
11542 void
11543 AdjournEvent()
11544 {
11545     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11546     
11547     if (appData.icsActive) {
11548         SendToICS(ics_prefix);
11549         SendToICS("adjourn\n");
11550     } else {
11551         /* Currently GNU Chess doesn't offer or accept Adjourns */
11552     }
11553 }
11554
11555
11556 void
11557 AbortEvent()
11558 {
11559     /* Offer Abort or accept pending Abort offer from opponent */
11560     
11561     if (appData.icsActive) {
11562         SendToICS(ics_prefix);
11563         SendToICS("abort\n");
11564     } else {
11565         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11566     }
11567 }
11568
11569 void
11570 ResignEvent()
11571 {
11572     /* Resign.  You can do this even if it's not your turn. */
11573     
11574     if (appData.icsActive) {
11575         SendToICS(ics_prefix);
11576         SendToICS("resign\n");
11577     } else {
11578         switch (gameMode) {
11579           case MachinePlaysWhite:
11580             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11581             break;
11582           case MachinePlaysBlack:
11583             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11584             break;
11585           case EditGame:
11586             if (cmailMsgLoaded) {
11587                 TruncateGame();
11588                 if (WhiteOnMove(cmailOldMove)) {
11589                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11590                 } else {
11591                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11592                 }
11593                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11594             }
11595             break;
11596           default:
11597             break;
11598         }
11599     }
11600 }
11601
11602
11603 void
11604 StopObservingEvent()
11605 {
11606     /* Stop observing current games */
11607     SendToICS(ics_prefix);
11608     SendToICS("unobserve\n");
11609 }
11610
11611 void
11612 StopExaminingEvent()
11613 {
11614     /* Stop observing current game */
11615     SendToICS(ics_prefix);
11616     SendToICS("unexamine\n");
11617 }
11618
11619 void
11620 ForwardInner(target)
11621      int target;
11622 {
11623     int limit;
11624
11625     if (appData.debugMode)
11626         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11627                 target, currentMove, forwardMostMove);
11628
11629     if (gameMode == EditPosition)
11630       return;
11631
11632     if (gameMode == PlayFromGameFile && !pausing)
11633       PauseEvent();
11634     
11635     if (gameMode == IcsExamining && pausing)
11636       limit = pauseExamForwardMostMove;
11637     else
11638       limit = forwardMostMove;
11639     
11640     if (target > limit) target = limit;
11641
11642     if (target > 0 && moveList[target - 1][0]) {
11643         int fromX, fromY, toX, toY;
11644         toX = moveList[target - 1][2] - AAA;
11645         toY = moveList[target - 1][3] - ONE;
11646         if (moveList[target - 1][1] == '@') {
11647             if (appData.highlightLastMove) {
11648                 SetHighlights(-1, -1, toX, toY);
11649             }
11650         } else {
11651             fromX = moveList[target - 1][0] - AAA;
11652             fromY = moveList[target - 1][1] - ONE;
11653             if (target == currentMove + 1) {
11654                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11655             }
11656             if (appData.highlightLastMove) {
11657                 SetHighlights(fromX, fromY, toX, toY);
11658             }
11659         }
11660     }
11661     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11662         gameMode == Training || gameMode == PlayFromGameFile || 
11663         gameMode == AnalyzeFile) {
11664         while (currentMove < target) {
11665             SendMoveToProgram(currentMove++, &first);
11666         }
11667     } else {
11668         currentMove = target;
11669     }
11670     
11671     if (gameMode == EditGame || gameMode == EndOfGame) {
11672         whiteTimeRemaining = timeRemaining[0][currentMove];
11673         blackTimeRemaining = timeRemaining[1][currentMove];
11674     }
11675     DisplayBothClocks();
11676     DisplayMove(currentMove - 1);
11677     DrawPosition(FALSE, boards[currentMove]);
11678     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11679     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11680         DisplayComment(currentMove - 1, commentList[currentMove]);
11681     }
11682 }
11683
11684
11685 void
11686 ForwardEvent()
11687 {
11688     if (gameMode == IcsExamining && !pausing) {
11689         SendToICS(ics_prefix);
11690         SendToICS("forward\n");
11691     } else {
11692         ForwardInner(currentMove + 1);
11693     }
11694 }
11695
11696 void
11697 ToEndEvent()
11698 {
11699     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11700         /* to optimze, we temporarily turn off analysis mode while we feed
11701          * the remaining moves to the engine. Otherwise we get analysis output
11702          * after each move.
11703          */ 
11704         if (first.analysisSupport) {
11705           SendToProgram("exit\nforce\n", &first);
11706           first.analyzing = FALSE;
11707         }
11708     }
11709         
11710     if (gameMode == IcsExamining && !pausing) {
11711         SendToICS(ics_prefix);
11712         SendToICS("forward 999999\n");
11713     } else {
11714         ForwardInner(forwardMostMove);
11715     }
11716
11717     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11718         /* we have fed all the moves, so reactivate analysis mode */
11719         SendToProgram("analyze\n", &first);
11720         first.analyzing = TRUE;
11721         /*first.maybeThinking = TRUE;*/
11722         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11723     }
11724 }
11725
11726 void
11727 BackwardInner(target)
11728      int target;
11729 {
11730     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11731
11732     if (appData.debugMode)
11733         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11734                 target, currentMove, forwardMostMove);
11735
11736     if (gameMode == EditPosition) return;
11737     if (currentMove <= backwardMostMove) {
11738         ClearHighlights();
11739         DrawPosition(full_redraw, boards[currentMove]);
11740         return;
11741     }
11742     if (gameMode == PlayFromGameFile && !pausing)
11743       PauseEvent();
11744     
11745     if (moveList[target][0]) {
11746         int fromX, fromY, toX, toY;
11747         toX = moveList[target][2] - AAA;
11748         toY = moveList[target][3] - ONE;
11749         if (moveList[target][1] == '@') {
11750             if (appData.highlightLastMove) {
11751                 SetHighlights(-1, -1, toX, toY);
11752             }
11753         } else {
11754             fromX = moveList[target][0] - AAA;
11755             fromY = moveList[target][1] - ONE;
11756             if (target == currentMove - 1) {
11757                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11758             }
11759             if (appData.highlightLastMove) {
11760                 SetHighlights(fromX, fromY, toX, toY);
11761             }
11762         }
11763     }
11764     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11765         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11766         while (currentMove > target) {
11767             SendToProgram("undo\n", &first);
11768             currentMove--;
11769         }
11770     } else {
11771         currentMove = target;
11772     }
11773     
11774     if (gameMode == EditGame || gameMode == EndOfGame) {
11775         whiteTimeRemaining = timeRemaining[0][currentMove];
11776         blackTimeRemaining = timeRemaining[1][currentMove];
11777     }
11778     DisplayBothClocks();
11779     DisplayMove(currentMove - 1);
11780     DrawPosition(full_redraw, boards[currentMove]);
11781     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11782     // [HGM] PV info: routine tests if comment empty
11783     DisplayComment(currentMove - 1, commentList[currentMove]);
11784 }
11785
11786 void
11787 BackwardEvent()
11788 {
11789     if (gameMode == IcsExamining && !pausing) {
11790         SendToICS(ics_prefix);
11791         SendToICS("backward\n");
11792     } else {
11793         BackwardInner(currentMove - 1);
11794     }
11795 }
11796
11797 void
11798 ToStartEvent()
11799 {
11800     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11801         /* to optimize, we temporarily turn off analysis mode while we undo
11802          * all the moves. Otherwise we get analysis output after each undo.
11803          */ 
11804         if (first.analysisSupport) {
11805           SendToProgram("exit\nforce\n", &first);
11806           first.analyzing = FALSE;
11807         }
11808     }
11809
11810     if (gameMode == IcsExamining && !pausing) {
11811         SendToICS(ics_prefix);
11812         SendToICS("backward 999999\n");
11813     } else {
11814         BackwardInner(backwardMostMove);
11815     }
11816
11817     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11818         /* we have fed all the moves, so reactivate analysis mode */
11819         SendToProgram("analyze\n", &first);
11820         first.analyzing = TRUE;
11821         /*first.maybeThinking = TRUE;*/
11822         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11823     }
11824 }
11825
11826 void
11827 ToNrEvent(int to)
11828 {
11829   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11830   if (to >= forwardMostMove) to = forwardMostMove;
11831   if (to <= backwardMostMove) to = backwardMostMove;
11832   if (to < currentMove) {
11833     BackwardInner(to);
11834   } else {
11835     ForwardInner(to);
11836   }
11837 }
11838
11839 void
11840 RevertEvent()
11841 {
11842     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11843         return;
11844     }
11845     if (gameMode != IcsExamining) {
11846         DisplayError(_("You are not examining a game"), 0);
11847         return;
11848     }
11849     if (pausing) {
11850         DisplayError(_("You can't revert while pausing"), 0);
11851         return;
11852     }
11853     SendToICS(ics_prefix);
11854     SendToICS("revert\n");
11855 }
11856
11857 void
11858 RetractMoveEvent()
11859 {
11860     switch (gameMode) {
11861       case MachinePlaysWhite:
11862       case MachinePlaysBlack:
11863         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11864             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11865             return;
11866         }
11867         if (forwardMostMove < 2) return;
11868         currentMove = forwardMostMove = forwardMostMove - 2;
11869         whiteTimeRemaining = timeRemaining[0][currentMove];
11870         blackTimeRemaining = timeRemaining[1][currentMove];
11871         DisplayBothClocks();
11872         DisplayMove(currentMove - 1);
11873         ClearHighlights();/*!! could figure this out*/
11874         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11875         SendToProgram("remove\n", &first);
11876         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11877         break;
11878
11879       case BeginningOfGame:
11880       default:
11881         break;
11882
11883       case IcsPlayingWhite:
11884       case IcsPlayingBlack:
11885         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11886             SendToICS(ics_prefix);
11887             SendToICS("takeback 2\n");
11888         } else {
11889             SendToICS(ics_prefix);
11890             SendToICS("takeback 1\n");
11891         }
11892         break;
11893     }
11894 }
11895
11896 void
11897 MoveNowEvent()
11898 {
11899     ChessProgramState *cps;
11900
11901     switch (gameMode) {
11902       case MachinePlaysWhite:
11903         if (!WhiteOnMove(forwardMostMove)) {
11904             DisplayError(_("It is your turn"), 0);
11905             return;
11906         }
11907         cps = &first;
11908         break;
11909       case MachinePlaysBlack:
11910         if (WhiteOnMove(forwardMostMove)) {
11911             DisplayError(_("It is your turn"), 0);
11912             return;
11913         }
11914         cps = &first;
11915         break;
11916       case TwoMachinesPlay:
11917         if (WhiteOnMove(forwardMostMove) ==
11918             (first.twoMachinesColor[0] == 'w')) {
11919             cps = &first;
11920         } else {
11921             cps = &second;
11922         }
11923         break;
11924       case BeginningOfGame:
11925       default:
11926         return;
11927     }
11928     SendToProgram("?\n", cps);
11929 }
11930
11931 void
11932 TruncateGameEvent()
11933 {
11934     EditGameEvent();
11935     if (gameMode != EditGame) return;
11936     TruncateGame();
11937 }
11938
11939 void
11940 TruncateGame()
11941 {
11942     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11943     if (forwardMostMove > currentMove) {
11944         if (gameInfo.resultDetails != NULL) {
11945             free(gameInfo.resultDetails);
11946             gameInfo.resultDetails = NULL;
11947             gameInfo.result = GameUnfinished;
11948         }
11949         forwardMostMove = currentMove;
11950         HistorySet(parseList, backwardMostMove, forwardMostMove,
11951                    currentMove-1);
11952     }
11953 }
11954
11955 void
11956 HintEvent()
11957 {
11958     if (appData.noChessProgram) return;
11959     switch (gameMode) {
11960       case MachinePlaysWhite:
11961         if (WhiteOnMove(forwardMostMove)) {
11962             DisplayError(_("Wait until your turn"), 0);
11963             return;
11964         }
11965         break;
11966       case BeginningOfGame:
11967       case MachinePlaysBlack:
11968         if (!WhiteOnMove(forwardMostMove)) {
11969             DisplayError(_("Wait until your turn"), 0);
11970             return;
11971         }
11972         break;
11973       default:
11974         DisplayError(_("No hint available"), 0);
11975         return;
11976     }
11977     SendToProgram("hint\n", &first);
11978     hintRequested = TRUE;
11979 }
11980
11981 void
11982 BookEvent()
11983 {
11984     if (appData.noChessProgram) return;
11985     switch (gameMode) {
11986       case MachinePlaysWhite:
11987         if (WhiteOnMove(forwardMostMove)) {
11988             DisplayError(_("Wait until your turn"), 0);
11989             return;
11990         }
11991         break;
11992       case BeginningOfGame:
11993       case MachinePlaysBlack:
11994         if (!WhiteOnMove(forwardMostMove)) {
11995             DisplayError(_("Wait until your turn"), 0);
11996             return;
11997         }
11998         break;
11999       case EditPosition:
12000         EditPositionDone(TRUE);
12001         break;
12002       case TwoMachinesPlay:
12003         return;
12004       default:
12005         break;
12006     }
12007     SendToProgram("bk\n", &first);
12008     bookOutput[0] = NULLCHAR;
12009     bookRequested = TRUE;
12010 }
12011
12012 void
12013 AboutGameEvent()
12014 {
12015     char *tags = PGNTags(&gameInfo);
12016     TagsPopUp(tags, CmailMsg());
12017     free(tags);
12018 }
12019
12020 /* end button procedures */
12021
12022 void
12023 PrintPosition(fp, move)
12024      FILE *fp;
12025      int move;
12026 {
12027     int i, j;
12028     
12029     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12030         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12031             char c = PieceToChar(boards[move][i][j]);
12032             fputc(c == 'x' ? '.' : c, fp);
12033             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12034         }
12035     }
12036     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12037       fprintf(fp, "white to play\n");
12038     else
12039       fprintf(fp, "black to play\n");
12040 }
12041
12042 void
12043 PrintOpponents(fp)
12044      FILE *fp;
12045 {
12046     if (gameInfo.white != NULL) {
12047         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12048     } else {
12049         fprintf(fp, "\n");
12050     }
12051 }
12052
12053 /* Find last component of program's own name, using some heuristics */
12054 void
12055 TidyProgramName(prog, host, buf)
12056      char *prog, *host, buf[MSG_SIZ];
12057 {
12058     char *p, *q;
12059     int local = (strcmp(host, "localhost") == 0);
12060     while (!local && (p = strchr(prog, ';')) != NULL) {
12061         p++;
12062         while (*p == ' ') p++;
12063         prog = p;
12064     }
12065     if (*prog == '"' || *prog == '\'') {
12066         q = strchr(prog + 1, *prog);
12067     } else {
12068         q = strchr(prog, ' ');
12069     }
12070     if (q == NULL) q = prog + strlen(prog);
12071     p = q;
12072     while (p >= prog && *p != '/' && *p != '\\') p--;
12073     p++;
12074     if(p == prog && *p == '"') p++;
12075     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12076     memcpy(buf, p, q - p);
12077     buf[q - p] = NULLCHAR;
12078     if (!local) {
12079         strcat(buf, "@");
12080         strcat(buf, host);
12081     }
12082 }
12083
12084 char *
12085 TimeControlTagValue()
12086 {
12087     char buf[MSG_SIZ];
12088     if (!appData.clockMode) {
12089         strcpy(buf, "-");
12090     } else if (movesPerSession > 0) {
12091         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12092     } else if (timeIncrement == 0) {
12093         sprintf(buf, "%ld", timeControl/1000);
12094     } else {
12095         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12096     }
12097     return StrSave(buf);
12098 }
12099
12100 void
12101 SetGameInfo()
12102 {
12103     /* This routine is used only for certain modes */
12104     VariantClass v = gameInfo.variant;
12105     ChessMove r = GameUnfinished;
12106     char *p = NULL;
12107
12108     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12109         r = gameInfo.result; 
12110         p = gameInfo.resultDetails; 
12111         gameInfo.resultDetails = NULL;
12112     }
12113     ClearGameInfo(&gameInfo);
12114     gameInfo.variant = v;
12115
12116     switch (gameMode) {
12117       case MachinePlaysWhite:
12118         gameInfo.event = StrSave( appData.pgnEventHeader );
12119         gameInfo.site = StrSave(HostName());
12120         gameInfo.date = PGNDate();
12121         gameInfo.round = StrSave("-");
12122         gameInfo.white = StrSave(first.tidy);
12123         gameInfo.black = StrSave(UserName());
12124         gameInfo.timeControl = TimeControlTagValue();
12125         break;
12126
12127       case MachinePlaysBlack:
12128         gameInfo.event = StrSave( appData.pgnEventHeader );
12129         gameInfo.site = StrSave(HostName());
12130         gameInfo.date = PGNDate();
12131         gameInfo.round = StrSave("-");
12132         gameInfo.white = StrSave(UserName());
12133         gameInfo.black = StrSave(first.tidy);
12134         gameInfo.timeControl = TimeControlTagValue();
12135         break;
12136
12137       case TwoMachinesPlay:
12138         gameInfo.event = StrSave( appData.pgnEventHeader );
12139         gameInfo.site = StrSave(HostName());
12140         gameInfo.date = PGNDate();
12141         if (matchGame > 0) {
12142             char buf[MSG_SIZ];
12143             sprintf(buf, "%d", matchGame);
12144             gameInfo.round = StrSave(buf);
12145         } else {
12146             gameInfo.round = StrSave("-");
12147         }
12148         if (first.twoMachinesColor[0] == 'w') {
12149             gameInfo.white = StrSave(first.tidy);
12150             gameInfo.black = StrSave(second.tidy);
12151         } else {
12152             gameInfo.white = StrSave(second.tidy);
12153             gameInfo.black = StrSave(first.tidy);
12154         }
12155         gameInfo.timeControl = TimeControlTagValue();
12156         break;
12157
12158       case EditGame:
12159         gameInfo.event = StrSave("Edited game");
12160         gameInfo.site = StrSave(HostName());
12161         gameInfo.date = PGNDate();
12162         gameInfo.round = StrSave("-");
12163         gameInfo.white = StrSave("-");
12164         gameInfo.black = StrSave("-");
12165         gameInfo.result = r;
12166         gameInfo.resultDetails = p;
12167         break;
12168
12169       case EditPosition:
12170         gameInfo.event = StrSave("Edited position");
12171         gameInfo.site = StrSave(HostName());
12172         gameInfo.date = PGNDate();
12173         gameInfo.round = StrSave("-");
12174         gameInfo.white = StrSave("-");
12175         gameInfo.black = StrSave("-");
12176         break;
12177
12178       case IcsPlayingWhite:
12179       case IcsPlayingBlack:
12180       case IcsObserving:
12181       case IcsExamining:
12182         break;
12183
12184       case PlayFromGameFile:
12185         gameInfo.event = StrSave("Game from non-PGN file");
12186         gameInfo.site = StrSave(HostName());
12187         gameInfo.date = PGNDate();
12188         gameInfo.round = StrSave("-");
12189         gameInfo.white = StrSave("?");
12190         gameInfo.black = StrSave("?");
12191         break;
12192
12193       default:
12194         break;
12195     }
12196 }
12197
12198 void
12199 ReplaceComment(index, text)
12200      int index;
12201      char *text;
12202 {
12203     int len;
12204
12205     while (*text == '\n') text++;
12206     len = strlen(text);
12207     while (len > 0 && text[len - 1] == '\n') len--;
12208
12209     if (commentList[index] != NULL)
12210       free(commentList[index]);
12211
12212     if (len == 0) {
12213         commentList[index] = NULL;
12214         return;
12215     }
12216   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12217       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12218       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12219     commentList[index] = (char *) malloc(len + 2);
12220     strncpy(commentList[index], text, len);
12221     commentList[index][len] = '\n';
12222     commentList[index][len + 1] = NULLCHAR;
12223   } else { 
12224     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12225     char *p;
12226     commentList[index] = (char *) malloc(len + 6);
12227     strcpy(commentList[index], "{\n");
12228     strncpy(commentList[index]+2, text, len);
12229     commentList[index][len+2] = NULLCHAR;
12230     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12231     strcat(commentList[index], "\n}\n");
12232   }
12233 }
12234
12235 void
12236 CrushCRs(text)
12237      char *text;
12238 {
12239   char *p = text;
12240   char *q = text;
12241   char ch;
12242
12243   do {
12244     ch = *p++;
12245     if (ch == '\r') continue;
12246     *q++ = ch;
12247   } while (ch != '\0');
12248 }
12249
12250 void
12251 AppendComment(index, text, addBraces)
12252      int index;
12253      char *text;
12254      Boolean addBraces; // [HGM] braces: tells if we should add {}
12255 {
12256     int oldlen, len;
12257     char *old;
12258
12259 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12260     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12261
12262     CrushCRs(text);
12263     while (*text == '\n') text++;
12264     len = strlen(text);
12265     while (len > 0 && text[len - 1] == '\n') len--;
12266
12267     if (len == 0) return;
12268
12269     if (commentList[index] != NULL) {
12270         old = commentList[index];
12271         oldlen = strlen(old);
12272         while(commentList[index][oldlen-1] ==  '\n')
12273           commentList[index][--oldlen] = NULLCHAR;
12274         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12275         strcpy(commentList[index], old);
12276         free(old);
12277         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12278         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12279           if(addBraces) addBraces = FALSE; else { text++; len--; }
12280           while (*text == '\n') { text++; len--; }
12281           commentList[index][--oldlen] = NULLCHAR;
12282       }
12283         if(addBraces) strcat(commentList[index], "\n{\n");
12284         else          strcat(commentList[index], "\n");
12285         strcat(commentList[index], text);
12286         if(addBraces) strcat(commentList[index], "\n}\n");
12287         else          strcat(commentList[index], "\n");
12288     } else {
12289         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12290         if(addBraces)
12291              strcpy(commentList[index], "{\n");
12292         else commentList[index][0] = NULLCHAR;
12293         strcat(commentList[index], text);
12294         strcat(commentList[index], "\n");
12295         if(addBraces) strcat(commentList[index], "}\n");
12296     }
12297 }
12298
12299 static char * FindStr( char * text, char * sub_text )
12300 {
12301     char * result = strstr( text, sub_text );
12302
12303     if( result != NULL ) {
12304         result += strlen( sub_text );
12305     }
12306
12307     return result;
12308 }
12309
12310 /* [AS] Try to extract PV info from PGN comment */
12311 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12312 char *GetInfoFromComment( int index, char * text )
12313 {
12314     char * sep = text;
12315
12316     if( text != NULL && index > 0 ) {
12317         int score = 0;
12318         int depth = 0;
12319         int time = -1, sec = 0, deci;
12320         char * s_eval = FindStr( text, "[%eval " );
12321         char * s_emt = FindStr( text, "[%emt " );
12322
12323         if( s_eval != NULL || s_emt != NULL ) {
12324             /* New style */
12325             char delim;
12326
12327             if( s_eval != NULL ) {
12328                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12329                     return text;
12330                 }
12331
12332                 if( delim != ']' ) {
12333                     return text;
12334                 }
12335             }
12336
12337             if( s_emt != NULL ) {
12338             }
12339                 return text;
12340         }
12341         else {
12342             /* We expect something like: [+|-]nnn.nn/dd */
12343             int score_lo = 0;
12344
12345             if(*text != '{') return text; // [HGM] braces: must be normal comment
12346
12347             sep = strchr( text, '/' );
12348             if( sep == NULL || sep < (text+4) ) {
12349                 return text;
12350             }
12351
12352             time = -1; sec = -1; deci = -1;
12353             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12354                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12355                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12356                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12357                 return text;
12358             }
12359
12360             if( score_lo < 0 || score_lo >= 100 ) {
12361                 return text;
12362             }
12363
12364             if(sec >= 0) time = 600*time + 10*sec; else
12365             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12366
12367             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12368
12369             /* [HGM] PV time: now locate end of PV info */
12370             while( *++sep >= '0' && *sep <= '9'); // strip depth
12371             if(time >= 0)
12372             while( *++sep >= '0' && *sep <= '9'); // strip time
12373             if(sec >= 0)
12374             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12375             if(deci >= 0)
12376             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12377             while(*sep == ' ') sep++;
12378         }
12379
12380         if( depth <= 0 ) {
12381             return text;
12382         }
12383
12384         if( time < 0 ) {
12385             time = -1;
12386         }
12387
12388         pvInfoList[index-1].depth = depth;
12389         pvInfoList[index-1].score = score;
12390         pvInfoList[index-1].time  = 10*time; // centi-sec
12391         if(*sep == '}') *sep = 0; else *--sep = '{';
12392     }
12393     return sep;
12394 }
12395
12396 void
12397 SendToProgram(message, cps)
12398      char *message;
12399      ChessProgramState *cps;
12400 {
12401     int count, outCount, error;
12402     char buf[MSG_SIZ];
12403
12404     if (cps->pr == NULL) return;
12405     Attention(cps);
12406     
12407     if (appData.debugMode) {
12408         TimeMark now;
12409         GetTimeMark(&now);
12410         fprintf(debugFP, "%ld >%-6s: %s", 
12411                 SubtractTimeMarks(&now, &programStartTime),
12412                 cps->which, message);
12413     }
12414     
12415     count = strlen(message);
12416     outCount = OutputToProcess(cps->pr, message, count, &error);
12417     if (outCount < count && !exiting 
12418                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12419         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12420         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12421             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12422                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12423                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12424             } else {
12425                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12426             }
12427             gameInfo.resultDetails = StrSave(buf);
12428         }
12429         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12430     }
12431 }
12432
12433 void
12434 ReceiveFromProgram(isr, closure, message, count, error)
12435      InputSourceRef isr;
12436      VOIDSTAR closure;
12437      char *message;
12438      int count;
12439      int error;
12440 {
12441     char *end_str;
12442     char buf[MSG_SIZ];
12443     ChessProgramState *cps = (ChessProgramState *)closure;
12444
12445     if (isr != cps->isr) return; /* Killed intentionally */
12446     if (count <= 0) {
12447         if (count == 0) {
12448             sprintf(buf,
12449                     _("Error: %s chess program (%s) exited unexpectedly"),
12450                     cps->which, cps->program);
12451         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12452                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12453                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12454                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12455                 } else {
12456                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12457                 }
12458                 gameInfo.resultDetails = StrSave(buf);
12459             }
12460             RemoveInputSource(cps->isr);
12461             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12462         } else {
12463             sprintf(buf,
12464                     _("Error reading from %s chess program (%s)"),
12465                     cps->which, cps->program);
12466             RemoveInputSource(cps->isr);
12467
12468             /* [AS] Program is misbehaving badly... kill it */
12469             if( count == -2 ) {
12470                 DestroyChildProcess( cps->pr, 9 );
12471                 cps->pr = NoProc;
12472             }
12473
12474             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12475         }
12476         return;
12477     }
12478     
12479     if ((end_str = strchr(message, '\r')) != NULL)
12480       *end_str = NULLCHAR;
12481     if ((end_str = strchr(message, '\n')) != NULL)
12482       *end_str = NULLCHAR;
12483     
12484     if (appData.debugMode) {
12485         TimeMark now; int print = 1;
12486         char *quote = ""; char c; int i;
12487
12488         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12489                 char start = message[0];
12490                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12491                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12492                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12493                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12494                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12495                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12496                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12497                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12498                         { quote = "# "; print = (appData.engineComments == 2); }
12499                 message[0] = start; // restore original message
12500         }
12501         if(print) {
12502                 GetTimeMark(&now);
12503                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12504                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12505                         quote,
12506                         message);
12507         }
12508     }
12509
12510     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12511     if (appData.icsEngineAnalyze) {
12512         if (strstr(message, "whisper") != NULL ||
12513              strstr(message, "kibitz") != NULL || 
12514             strstr(message, "tellics") != NULL) return;
12515     }
12516
12517     HandleMachineMove(message, cps);
12518 }
12519
12520
12521 void
12522 SendTimeControl(cps, mps, tc, inc, sd, st)
12523      ChessProgramState *cps;
12524      int mps, inc, sd, st;
12525      long tc;
12526 {
12527     char buf[MSG_SIZ];
12528     int seconds;
12529
12530     if( timeControl_2 > 0 ) {
12531         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12532             tc = timeControl_2;
12533         }
12534     }
12535     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12536     inc /= cps->timeOdds;
12537     st  /= cps->timeOdds;
12538
12539     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12540
12541     if (st > 0) {
12542       /* Set exact time per move, normally using st command */
12543       if (cps->stKludge) {
12544         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12545         seconds = st % 60;
12546         if (seconds == 0) {
12547           sprintf(buf, "level 1 %d\n", st/60);
12548         } else {
12549           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12550         }
12551       } else {
12552         sprintf(buf, "st %d\n", st);
12553       }
12554     } else {
12555       /* Set conventional or incremental time control, using level command */
12556       if (seconds == 0) {
12557         /* Note old gnuchess bug -- minutes:seconds used to not work.
12558            Fixed in later versions, but still avoid :seconds
12559            when seconds is 0. */
12560         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12561       } else {
12562         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12563                 seconds, inc/1000);
12564       }
12565     }
12566     SendToProgram(buf, cps);
12567
12568     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12569     /* Orthogonally, limit search to given depth */
12570     if (sd > 0) {
12571       if (cps->sdKludge) {
12572         sprintf(buf, "depth\n%d\n", sd);
12573       } else {
12574         sprintf(buf, "sd %d\n", sd);
12575       }
12576       SendToProgram(buf, cps);
12577     }
12578
12579     if(cps->nps > 0) { /* [HGM] nps */
12580         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12581         else {
12582                 sprintf(buf, "nps %d\n", cps->nps);
12583               SendToProgram(buf, cps);
12584         }
12585     }
12586 }
12587
12588 ChessProgramState *WhitePlayer()
12589 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12590 {
12591     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12592        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12593         return &second;
12594     return &first;
12595 }
12596
12597 void
12598 SendTimeRemaining(cps, machineWhite)
12599      ChessProgramState *cps;
12600      int /*boolean*/ machineWhite;
12601 {
12602     char message[MSG_SIZ];
12603     long time, otime;
12604
12605     /* Note: this routine must be called when the clocks are stopped
12606        or when they have *just* been set or switched; otherwise
12607        it will be off by the time since the current tick started.
12608     */
12609     if (machineWhite) {
12610         time = whiteTimeRemaining / 10;
12611         otime = blackTimeRemaining / 10;
12612     } else {
12613         time = blackTimeRemaining / 10;
12614         otime = whiteTimeRemaining / 10;
12615     }
12616     /* [HGM] translate opponent's time by time-odds factor */
12617     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12618     if (appData.debugMode) {
12619         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12620     }
12621
12622     if (time <= 0) time = 1;
12623     if (otime <= 0) otime = 1;
12624     
12625     sprintf(message, "time %ld\n", time);
12626     SendToProgram(message, cps);
12627
12628     sprintf(message, "otim %ld\n", otime);
12629     SendToProgram(message, cps);
12630 }
12631
12632 int
12633 BoolFeature(p, name, loc, cps)
12634      char **p;
12635      char *name;
12636      int *loc;
12637      ChessProgramState *cps;
12638 {
12639   char buf[MSG_SIZ];
12640   int len = strlen(name);
12641   int val;
12642   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12643     (*p) += len + 1;
12644     sscanf(*p, "%d", &val);
12645     *loc = (val != 0);
12646     while (**p && **p != ' ') (*p)++;
12647     sprintf(buf, "accepted %s\n", name);
12648     SendToProgram(buf, cps);
12649     return TRUE;
12650   }
12651   return FALSE;
12652 }
12653
12654 int
12655 IntFeature(p, name, loc, cps)
12656      char **p;
12657      char *name;
12658      int *loc;
12659      ChessProgramState *cps;
12660 {
12661   char buf[MSG_SIZ];
12662   int len = strlen(name);
12663   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12664     (*p) += len + 1;
12665     sscanf(*p, "%d", loc);
12666     while (**p && **p != ' ') (*p)++;
12667     sprintf(buf, "accepted %s\n", name);
12668     SendToProgram(buf, cps);
12669     return TRUE;
12670   }
12671   return FALSE;
12672 }
12673
12674 int
12675 StringFeature(p, name, loc, cps)
12676      char **p;
12677      char *name;
12678      char loc[];
12679      ChessProgramState *cps;
12680 {
12681   char buf[MSG_SIZ];
12682   int len = strlen(name);
12683   if (strncmp((*p), name, len) == 0
12684       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12685     (*p) += len + 2;
12686     sscanf(*p, "%[^\"]", loc);
12687     while (**p && **p != '\"') (*p)++;
12688     if (**p == '\"') (*p)++;
12689     sprintf(buf, "accepted %s\n", name);
12690     SendToProgram(buf, cps);
12691     return TRUE;
12692   }
12693   return FALSE;
12694 }
12695
12696 int 
12697 ParseOption(Option *opt, ChessProgramState *cps)
12698 // [HGM] options: process the string that defines an engine option, and determine
12699 // name, type, default value, and allowed value range
12700 {
12701         char *p, *q, buf[MSG_SIZ];
12702         int n, min = (-1)<<31, max = 1<<31, def;
12703
12704         if(p = strstr(opt->name, " -spin ")) {
12705             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12706             if(max < min) max = min; // enforce consistency
12707             if(def < min) def = min;
12708             if(def > max) def = max;
12709             opt->value = def;
12710             opt->min = min;
12711             opt->max = max;
12712             opt->type = Spin;
12713         } else if((p = strstr(opt->name, " -slider "))) {
12714             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12715             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12716             if(max < min) max = min; // enforce consistency
12717             if(def < min) def = min;
12718             if(def > max) def = max;
12719             opt->value = def;
12720             opt->min = min;
12721             opt->max = max;
12722             opt->type = Spin; // Slider;
12723         } else if((p = strstr(opt->name, " -string "))) {
12724             opt->textValue = p+9;
12725             opt->type = TextBox;
12726         } else if((p = strstr(opt->name, " -file "))) {
12727             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12728             opt->textValue = p+7;
12729             opt->type = TextBox; // FileName;
12730         } else if((p = strstr(opt->name, " -path "))) {
12731             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12732             opt->textValue = p+7;
12733             opt->type = TextBox; // PathName;
12734         } else if(p = strstr(opt->name, " -check ")) {
12735             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12736             opt->value = (def != 0);
12737             opt->type = CheckBox;
12738         } else if(p = strstr(opt->name, " -combo ")) {
12739             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12740             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12741             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12742             opt->value = n = 0;
12743             while(q = StrStr(q, " /// ")) {
12744                 n++; *q = 0;    // count choices, and null-terminate each of them
12745                 q += 5;
12746                 if(*q == '*') { // remember default, which is marked with * prefix
12747                     q++;
12748                     opt->value = n;
12749                 }
12750                 cps->comboList[cps->comboCnt++] = q;
12751             }
12752             cps->comboList[cps->comboCnt++] = NULL;
12753             opt->max = n + 1;
12754             opt->type = ComboBox;
12755         } else if(p = strstr(opt->name, " -button")) {
12756             opt->type = Button;
12757         } else if(p = strstr(opt->name, " -save")) {
12758             opt->type = SaveButton;
12759         } else return FALSE;
12760         *p = 0; // terminate option name
12761         // now look if the command-line options define a setting for this engine option.
12762         if(cps->optionSettings && cps->optionSettings[0])
12763             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12764         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12765                 sprintf(buf, "option %s", p);
12766                 if(p = strstr(buf, ",")) *p = 0;
12767                 strcat(buf, "\n");
12768                 SendToProgram(buf, cps);
12769         }
12770         return TRUE;
12771 }
12772
12773 void
12774 FeatureDone(cps, val)
12775      ChessProgramState* cps;
12776      int val;
12777 {
12778   DelayedEventCallback cb = GetDelayedEvent();
12779   if ((cb == InitBackEnd3 && cps == &first) ||
12780       (cb == TwoMachinesEventIfReady && cps == &second)) {
12781     CancelDelayedEvent();
12782     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12783   }
12784   cps->initDone = val;
12785 }
12786
12787 /* Parse feature command from engine */
12788 void
12789 ParseFeatures(args, cps)
12790      char* args;
12791      ChessProgramState *cps;  
12792 {
12793   char *p = args;
12794   char *q;
12795   int val;
12796   char buf[MSG_SIZ];
12797
12798   for (;;) {
12799     while (*p == ' ') p++;
12800     if (*p == NULLCHAR) return;
12801
12802     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12803     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12804     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12805     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12806     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12807     if (BoolFeature(&p, "reuse", &val, cps)) {
12808       /* Engine can disable reuse, but can't enable it if user said no */
12809       if (!val) cps->reuse = FALSE;
12810       continue;
12811     }
12812     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12813     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12814       if (gameMode == TwoMachinesPlay) {
12815         DisplayTwoMachinesTitle();
12816       } else {
12817         DisplayTitle("");
12818       }
12819       continue;
12820     }
12821     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12822     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12823     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12824     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12825     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12826     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12827     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12828     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12829     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12830     if (IntFeature(&p, "done", &val, cps)) {
12831       FeatureDone(cps, val);
12832       continue;
12833     }
12834     /* Added by Tord: */
12835     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12836     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12837     /* End of additions by Tord */
12838
12839     /* [HGM] added features: */
12840     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12841     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12842     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12843     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12844     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12845     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12846     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12847         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12848             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12849             SendToProgram(buf, cps);
12850             continue;
12851         }
12852         if(cps->nrOptions >= MAX_OPTIONS) {
12853             cps->nrOptions--;
12854             sprintf(buf, "%s engine has too many options\n", cps->which);
12855             DisplayError(buf, 0);
12856         }
12857         continue;
12858     }
12859     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12860     /* End of additions by HGM */
12861
12862     /* unknown feature: complain and skip */
12863     q = p;
12864     while (*q && *q != '=') q++;
12865     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12866     SendToProgram(buf, cps);
12867     p = q;
12868     if (*p == '=') {
12869       p++;
12870       if (*p == '\"') {
12871         p++;
12872         while (*p && *p != '\"') p++;
12873         if (*p == '\"') p++;
12874       } else {
12875         while (*p && *p != ' ') p++;
12876       }
12877     }
12878   }
12879
12880 }
12881
12882 void
12883 PeriodicUpdatesEvent(newState)
12884      int newState;
12885 {
12886     if (newState == appData.periodicUpdates)
12887       return;
12888
12889     appData.periodicUpdates=newState;
12890
12891     /* Display type changes, so update it now */
12892 //    DisplayAnalysis();
12893
12894     /* Get the ball rolling again... */
12895     if (newState) {
12896         AnalysisPeriodicEvent(1);
12897         StartAnalysisClock();
12898     }
12899 }
12900
12901 void
12902 PonderNextMoveEvent(newState)
12903      int newState;
12904 {
12905     if (newState == appData.ponderNextMove) return;
12906     if (gameMode == EditPosition) EditPositionDone(TRUE);
12907     if (newState) {
12908         SendToProgram("hard\n", &first);
12909         if (gameMode == TwoMachinesPlay) {
12910             SendToProgram("hard\n", &second);
12911         }
12912     } else {
12913         SendToProgram("easy\n", &first);
12914         thinkOutput[0] = NULLCHAR;
12915         if (gameMode == TwoMachinesPlay) {
12916             SendToProgram("easy\n", &second);
12917         }
12918     }
12919     appData.ponderNextMove = newState;
12920 }
12921
12922 void
12923 NewSettingEvent(option, command, value)
12924      char *command;
12925      int option, value;
12926 {
12927     char buf[MSG_SIZ];
12928
12929     if (gameMode == EditPosition) EditPositionDone(TRUE);
12930     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12931     SendToProgram(buf, &first);
12932     if (gameMode == TwoMachinesPlay) {
12933         SendToProgram(buf, &second);
12934     }
12935 }
12936
12937 void
12938 ShowThinkingEvent()
12939 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12940 {
12941     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12942     int newState = appData.showThinking
12943         // [HGM] thinking: other features now need thinking output as well
12944         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12945     
12946     if (oldState == newState) return;
12947     oldState = newState;
12948     if (gameMode == EditPosition) EditPositionDone(TRUE);
12949     if (oldState) {
12950         SendToProgram("post\n", &first);
12951         if (gameMode == TwoMachinesPlay) {
12952             SendToProgram("post\n", &second);
12953         }
12954     } else {
12955         SendToProgram("nopost\n", &first);
12956         thinkOutput[0] = NULLCHAR;
12957         if (gameMode == TwoMachinesPlay) {
12958             SendToProgram("nopost\n", &second);
12959         }
12960     }
12961 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12962 }
12963
12964 void
12965 AskQuestionEvent(title, question, replyPrefix, which)
12966      char *title; char *question; char *replyPrefix; char *which;
12967 {
12968   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12969   if (pr == NoProc) return;
12970   AskQuestion(title, question, replyPrefix, pr);
12971 }
12972
12973 void
12974 DisplayMove(moveNumber)
12975      int moveNumber;
12976 {
12977     char message[MSG_SIZ];
12978     char res[MSG_SIZ];
12979     char cpThinkOutput[MSG_SIZ];
12980
12981     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12982     
12983     if (moveNumber == forwardMostMove - 1 || 
12984         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12985
12986         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12987
12988         if (strchr(cpThinkOutput, '\n')) {
12989             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12990         }
12991     } else {
12992         *cpThinkOutput = NULLCHAR;
12993     }
12994
12995     /* [AS] Hide thinking from human user */
12996     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12997         *cpThinkOutput = NULLCHAR;
12998         if( thinkOutput[0] != NULLCHAR ) {
12999             int i;
13000
13001             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13002                 cpThinkOutput[i] = '.';
13003             }
13004             cpThinkOutput[i] = NULLCHAR;
13005             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13006         }
13007     }
13008
13009     if (moveNumber == forwardMostMove - 1 &&
13010         gameInfo.resultDetails != NULL) {
13011         if (gameInfo.resultDetails[0] == NULLCHAR) {
13012             sprintf(res, " %s", PGNResult(gameInfo.result));
13013         } else {
13014             sprintf(res, " {%s} %s",
13015                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13016         }
13017     } else {
13018         res[0] = NULLCHAR;
13019     }
13020
13021     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13022         DisplayMessage(res, cpThinkOutput);
13023     } else {
13024         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13025                 WhiteOnMove(moveNumber) ? " " : ".. ",
13026                 parseList[moveNumber], res);
13027         DisplayMessage(message, cpThinkOutput);
13028     }
13029 }
13030
13031 void
13032 DisplayComment(moveNumber, text)
13033      int moveNumber;
13034      char *text;
13035 {
13036     char title[MSG_SIZ];
13037     char buf[8000]; // comment can be long!
13038     int score, depth;
13039     
13040     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13041       strcpy(title, "Comment");
13042     } else {
13043       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13044               WhiteOnMove(moveNumber) ? " " : ".. ",
13045               parseList[moveNumber]);
13046     }
13047     // [HGM] PV info: display PV info together with (or as) comment
13048     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13049       if(text == NULL) text = "";                                           
13050       score = pvInfoList[moveNumber].score;
13051       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13052               depth, (pvInfoList[moveNumber].time+50)/100, text);
13053       text = buf;
13054     }
13055     if (text != NULL && (appData.autoDisplayComment || commentUp))
13056         CommentPopUp(title, text);
13057 }
13058
13059 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13060  * might be busy thinking or pondering.  It can be omitted if your
13061  * gnuchess is configured to stop thinking immediately on any user
13062  * input.  However, that gnuchess feature depends on the FIONREAD
13063  * ioctl, which does not work properly on some flavors of Unix.
13064  */
13065 void
13066 Attention(cps)
13067      ChessProgramState *cps;
13068 {
13069 #if ATTENTION
13070     if (!cps->useSigint) return;
13071     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13072     switch (gameMode) {
13073       case MachinePlaysWhite:
13074       case MachinePlaysBlack:
13075       case TwoMachinesPlay:
13076       case IcsPlayingWhite:
13077       case IcsPlayingBlack:
13078       case AnalyzeMode:
13079       case AnalyzeFile:
13080         /* Skip if we know it isn't thinking */
13081         if (!cps->maybeThinking) return;
13082         if (appData.debugMode)
13083           fprintf(debugFP, "Interrupting %s\n", cps->which);
13084         InterruptChildProcess(cps->pr);
13085         cps->maybeThinking = FALSE;
13086         break;
13087       default:
13088         break;
13089     }
13090 #endif /*ATTENTION*/
13091 }
13092
13093 int
13094 CheckFlags()
13095 {
13096     if (whiteTimeRemaining <= 0) {
13097         if (!whiteFlag) {
13098             whiteFlag = TRUE;
13099             if (appData.icsActive) {
13100                 if (appData.autoCallFlag &&
13101                     gameMode == IcsPlayingBlack && !blackFlag) {
13102                   SendToICS(ics_prefix);
13103                   SendToICS("flag\n");
13104                 }
13105             } else {
13106                 if (blackFlag) {
13107                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13108                 } else {
13109                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13110                     if (appData.autoCallFlag) {
13111                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13112                         return TRUE;
13113                     }
13114                 }
13115             }
13116         }
13117     }
13118     if (blackTimeRemaining <= 0) {
13119         if (!blackFlag) {
13120             blackFlag = TRUE;
13121             if (appData.icsActive) {
13122                 if (appData.autoCallFlag &&
13123                     gameMode == IcsPlayingWhite && !whiteFlag) {
13124                   SendToICS(ics_prefix);
13125                   SendToICS("flag\n");
13126                 }
13127             } else {
13128                 if (whiteFlag) {
13129                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13130                 } else {
13131                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13132                     if (appData.autoCallFlag) {
13133                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13134                         return TRUE;
13135                     }
13136                 }
13137             }
13138         }
13139     }
13140     return FALSE;
13141 }
13142
13143 void
13144 CheckTimeControl()
13145 {
13146     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13147         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13148
13149     /*
13150      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13151      */
13152     if ( !WhiteOnMove(forwardMostMove) )
13153         /* White made time control */
13154         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13155         /* [HGM] time odds: correct new time quota for time odds! */
13156                                             / WhitePlayer()->timeOdds;
13157       else
13158         /* Black made time control */
13159         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13160                                             / WhitePlayer()->other->timeOdds;
13161 }
13162
13163 void
13164 DisplayBothClocks()
13165 {
13166     int wom = gameMode == EditPosition ?
13167       !blackPlaysFirst : WhiteOnMove(currentMove);
13168     DisplayWhiteClock(whiteTimeRemaining, wom);
13169     DisplayBlackClock(blackTimeRemaining, !wom);
13170 }
13171
13172
13173 /* Timekeeping seems to be a portability nightmare.  I think everyone
13174    has ftime(), but I'm really not sure, so I'm including some ifdefs
13175    to use other calls if you don't.  Clocks will be less accurate if
13176    you have neither ftime nor gettimeofday.
13177 */
13178
13179 /* VS 2008 requires the #include outside of the function */
13180 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13181 #include <sys/timeb.h>
13182 #endif
13183
13184 /* Get the current time as a TimeMark */
13185 void
13186 GetTimeMark(tm)
13187      TimeMark *tm;
13188 {
13189 #if HAVE_GETTIMEOFDAY
13190
13191     struct timeval timeVal;
13192     struct timezone timeZone;
13193
13194     gettimeofday(&timeVal, &timeZone);
13195     tm->sec = (long) timeVal.tv_sec; 
13196     tm->ms = (int) (timeVal.tv_usec / 1000L);
13197
13198 #else /*!HAVE_GETTIMEOFDAY*/
13199 #if HAVE_FTIME
13200
13201 // include <sys/timeb.h> / moved to just above start of function
13202     struct timeb timeB;
13203
13204     ftime(&timeB);
13205     tm->sec = (long) timeB.time;
13206     tm->ms = (int) timeB.millitm;
13207
13208 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13209     tm->sec = (long) time(NULL);
13210     tm->ms = 0;
13211 #endif
13212 #endif
13213 }
13214
13215 /* Return the difference in milliseconds between two
13216    time marks.  We assume the difference will fit in a long!
13217 */
13218 long
13219 SubtractTimeMarks(tm2, tm1)
13220      TimeMark *tm2, *tm1;
13221 {
13222     return 1000L*(tm2->sec - tm1->sec) +
13223            (long) (tm2->ms - tm1->ms);
13224 }
13225
13226
13227 /*
13228  * Code to manage the game clocks.
13229  *
13230  * In tournament play, black starts the clock and then white makes a move.
13231  * We give the human user a slight advantage if he is playing white---the
13232  * clocks don't run until he makes his first move, so it takes zero time.
13233  * Also, we don't account for network lag, so we could get out of sync
13234  * with GNU Chess's clock -- but then, referees are always right.  
13235  */
13236
13237 static TimeMark tickStartTM;
13238 static long intendedTickLength;
13239
13240 long
13241 NextTickLength(timeRemaining)
13242      long timeRemaining;
13243 {
13244     long nominalTickLength, nextTickLength;
13245
13246     if (timeRemaining > 0L && timeRemaining <= 10000L)
13247       nominalTickLength = 100L;
13248     else
13249       nominalTickLength = 1000L;
13250     nextTickLength = timeRemaining % nominalTickLength;
13251     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13252
13253     return nextTickLength;
13254 }
13255
13256 /* Adjust clock one minute up or down */
13257 void
13258 AdjustClock(Boolean which, int dir)
13259 {
13260     if(which) blackTimeRemaining += 60000*dir;
13261     else      whiteTimeRemaining += 60000*dir;
13262     DisplayBothClocks();
13263 }
13264
13265 /* Stop clocks and reset to a fresh time control */
13266 void
13267 ResetClocks() 
13268 {
13269     (void) StopClockTimer();
13270     if (appData.icsActive) {
13271         whiteTimeRemaining = blackTimeRemaining = 0;
13272     } else if (searchTime) {
13273         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13274         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13275     } else { /* [HGM] correct new time quote for time odds */
13276         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13277         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13278     }
13279     if (whiteFlag || blackFlag) {
13280         DisplayTitle("");
13281         whiteFlag = blackFlag = FALSE;
13282     }
13283     DisplayBothClocks();
13284 }
13285
13286 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13287
13288 /* Decrement running clock by amount of time that has passed */
13289 void
13290 DecrementClocks()
13291 {
13292     long timeRemaining;
13293     long lastTickLength, fudge;
13294     TimeMark now;
13295
13296     if (!appData.clockMode) return;
13297     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13298         
13299     GetTimeMark(&now);
13300
13301     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13302
13303     /* Fudge if we woke up a little too soon */
13304     fudge = intendedTickLength - lastTickLength;
13305     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13306
13307     if (WhiteOnMove(forwardMostMove)) {
13308         if(whiteNPS >= 0) lastTickLength = 0;
13309         timeRemaining = whiteTimeRemaining -= lastTickLength;
13310         DisplayWhiteClock(whiteTimeRemaining - fudge,
13311                           WhiteOnMove(currentMove));
13312     } else {
13313         if(blackNPS >= 0) lastTickLength = 0;
13314         timeRemaining = blackTimeRemaining -= lastTickLength;
13315         DisplayBlackClock(blackTimeRemaining - fudge,
13316                           !WhiteOnMove(currentMove));
13317     }
13318
13319     if (CheckFlags()) return;
13320         
13321     tickStartTM = now;
13322     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13323     StartClockTimer(intendedTickLength);
13324
13325     /* if the time remaining has fallen below the alarm threshold, sound the
13326      * alarm. if the alarm has sounded and (due to a takeback or time control
13327      * with increment) the time remaining has increased to a level above the
13328      * threshold, reset the alarm so it can sound again. 
13329      */
13330     
13331     if (appData.icsActive && appData.icsAlarm) {
13332
13333         /* make sure we are dealing with the user's clock */
13334         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13335                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13336            )) return;
13337
13338         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13339             alarmSounded = FALSE;
13340         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13341             PlayAlarmSound();
13342             alarmSounded = TRUE;
13343         }
13344     }
13345 }
13346
13347
13348 /* A player has just moved, so stop the previously running
13349    clock and (if in clock mode) start the other one.
13350    We redisplay both clocks in case we're in ICS mode, because
13351    ICS gives us an update to both clocks after every move.
13352    Note that this routine is called *after* forwardMostMove
13353    is updated, so the last fractional tick must be subtracted
13354    from the color that is *not* on move now.
13355 */
13356 void
13357 SwitchClocks()
13358 {
13359     long lastTickLength;
13360     TimeMark now;
13361     int flagged = FALSE;
13362
13363     GetTimeMark(&now);
13364
13365     if (StopClockTimer() && appData.clockMode) {
13366         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13367         if (WhiteOnMove(forwardMostMove)) {
13368             if(blackNPS >= 0) lastTickLength = 0;
13369             blackTimeRemaining -= lastTickLength;
13370            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13371 //         if(pvInfoList[forwardMostMove-1].time == -1)
13372                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13373                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13374         } else {
13375            if(whiteNPS >= 0) lastTickLength = 0;
13376            whiteTimeRemaining -= lastTickLength;
13377            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13378 //         if(pvInfoList[forwardMostMove-1].time == -1)
13379                  pvInfoList[forwardMostMove-1].time = 
13380                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13381         }
13382         flagged = CheckFlags();
13383     }
13384     CheckTimeControl();
13385
13386     if (flagged || !appData.clockMode) return;
13387
13388     switch (gameMode) {
13389       case MachinePlaysBlack:
13390       case MachinePlaysWhite:
13391       case BeginningOfGame:
13392         if (pausing) return;
13393         break;
13394
13395       case EditGame:
13396       case PlayFromGameFile:
13397       case IcsExamining:
13398         return;
13399
13400       default:
13401         break;
13402     }
13403
13404     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13405         if(WhiteOnMove(forwardMostMove))
13406              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13407         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13408     }
13409
13410     tickStartTM = now;
13411     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13412       whiteTimeRemaining : blackTimeRemaining);
13413     StartClockTimer(intendedTickLength);
13414 }
13415         
13416
13417 /* Stop both clocks */
13418 void
13419 StopClocks()
13420 {       
13421     long lastTickLength;
13422     TimeMark now;
13423
13424     if (!StopClockTimer()) return;
13425     if (!appData.clockMode) return;
13426
13427     GetTimeMark(&now);
13428
13429     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13430     if (WhiteOnMove(forwardMostMove)) {
13431         if(whiteNPS >= 0) lastTickLength = 0;
13432         whiteTimeRemaining -= lastTickLength;
13433         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13434     } else {
13435         if(blackNPS >= 0) lastTickLength = 0;
13436         blackTimeRemaining -= lastTickLength;
13437         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13438     }
13439     CheckFlags();
13440 }
13441         
13442 /* Start clock of player on move.  Time may have been reset, so
13443    if clock is already running, stop and restart it. */
13444 void
13445 StartClocks()
13446 {
13447     (void) StopClockTimer(); /* in case it was running already */
13448     DisplayBothClocks();
13449     if (CheckFlags()) return;
13450
13451     if (!appData.clockMode) return;
13452     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13453
13454     GetTimeMark(&tickStartTM);
13455     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13456       whiteTimeRemaining : blackTimeRemaining);
13457
13458    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13459     whiteNPS = blackNPS = -1; 
13460     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13461        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13462         whiteNPS = first.nps;
13463     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13464        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13465         blackNPS = first.nps;
13466     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13467         whiteNPS = second.nps;
13468     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13469         blackNPS = second.nps;
13470     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13471
13472     StartClockTimer(intendedTickLength);
13473 }
13474
13475 char *
13476 TimeString(ms)
13477      long ms;
13478 {
13479     long second, minute, hour, day;
13480     char *sign = "";
13481     static char buf[32];
13482     
13483     if (ms > 0 && ms <= 9900) {
13484       /* convert milliseconds to tenths, rounding up */
13485       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13486
13487       sprintf(buf, " %03.1f ", tenths/10.0);
13488       return buf;
13489     }
13490
13491     /* convert milliseconds to seconds, rounding up */
13492     /* use floating point to avoid strangeness of integer division
13493        with negative dividends on many machines */
13494     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13495
13496     if (second < 0) {
13497         sign = "-";
13498         second = -second;
13499     }
13500     
13501     day = second / (60 * 60 * 24);
13502     second = second % (60 * 60 * 24);
13503     hour = second / (60 * 60);
13504     second = second % (60 * 60);
13505     minute = second / 60;
13506     second = second % 60;
13507     
13508     if (day > 0)
13509       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13510               sign, day, hour, minute, second);
13511     else if (hour > 0)
13512       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13513     else
13514       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13515     
13516     return buf;
13517 }
13518
13519
13520 /*
13521  * This is necessary because some C libraries aren't ANSI C compliant yet.
13522  */
13523 char *
13524 StrStr(string, match)
13525      char *string, *match;
13526 {
13527     int i, length;
13528     
13529     length = strlen(match);
13530     
13531     for (i = strlen(string) - length; i >= 0; i--, string++)
13532       if (!strncmp(match, string, length))
13533         return string;
13534     
13535     return NULL;
13536 }
13537
13538 char *
13539 StrCaseStr(string, match)
13540      char *string, *match;
13541 {
13542     int i, j, length;
13543     
13544     length = strlen(match);
13545     
13546     for (i = strlen(string) - length; i >= 0; i--, string++) {
13547         for (j = 0; j < length; j++) {
13548             if (ToLower(match[j]) != ToLower(string[j]))
13549               break;
13550         }
13551         if (j == length) return string;
13552     }
13553
13554     return NULL;
13555 }
13556
13557 #ifndef _amigados
13558 int
13559 StrCaseCmp(s1, s2)
13560      char *s1, *s2;
13561 {
13562     char c1, c2;
13563     
13564     for (;;) {
13565         c1 = ToLower(*s1++);
13566         c2 = ToLower(*s2++);
13567         if (c1 > c2) return 1;
13568         if (c1 < c2) return -1;
13569         if (c1 == NULLCHAR) return 0;
13570     }
13571 }
13572
13573
13574 int
13575 ToLower(c)
13576      int c;
13577 {
13578     return isupper(c) ? tolower(c) : c;
13579 }
13580
13581
13582 int
13583 ToUpper(c)
13584      int c;
13585 {
13586     return islower(c) ? toupper(c) : c;
13587 }
13588 #endif /* !_amigados    */
13589
13590 char *
13591 StrSave(s)
13592      char *s;
13593 {
13594     char *ret;
13595
13596     if ((ret = (char *) malloc(strlen(s) + 1))) {
13597         strcpy(ret, s);
13598     }
13599     return ret;
13600 }
13601
13602 char *
13603 StrSavePtr(s, savePtr)
13604      char *s, **savePtr;
13605 {
13606     if (*savePtr) {
13607         free(*savePtr);
13608     }
13609     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13610         strcpy(*savePtr, s);
13611     }
13612     return(*savePtr);
13613 }
13614
13615 char *
13616 PGNDate()
13617 {
13618     time_t clock;
13619     struct tm *tm;
13620     char buf[MSG_SIZ];
13621
13622     clock = time((time_t *)NULL);
13623     tm = localtime(&clock);
13624     sprintf(buf, "%04d.%02d.%02d",
13625             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13626     return StrSave(buf);
13627 }
13628
13629
13630 char *
13631 PositionToFEN(move, overrideCastling)
13632      int move;
13633      char *overrideCastling;
13634 {
13635     int i, j, fromX, fromY, toX, toY;
13636     int whiteToPlay;
13637     char buf[128];
13638     char *p, *q;
13639     int emptycount;
13640     ChessSquare piece;
13641
13642     whiteToPlay = (gameMode == EditPosition) ?
13643       !blackPlaysFirst : (move % 2 == 0);
13644     p = buf;
13645
13646     /* Piece placement data */
13647     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13648         emptycount = 0;
13649         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13650             if (boards[move][i][j] == EmptySquare) {
13651                 emptycount++;
13652             } else { ChessSquare piece = boards[move][i][j];
13653                 if (emptycount > 0) {
13654                     if(emptycount<10) /* [HGM] can be >= 10 */
13655                         *p++ = '0' + emptycount;
13656                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13657                     emptycount = 0;
13658                 }
13659                 if(PieceToChar(piece) == '+') {
13660                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13661                     *p++ = '+';
13662                     piece = (ChessSquare)(DEMOTED piece);
13663                 } 
13664                 *p++ = PieceToChar(piece);
13665                 if(p[-1] == '~') {
13666                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13667                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13668                     *p++ = '~';
13669                 }
13670             }
13671         }
13672         if (emptycount > 0) {
13673             if(emptycount<10) /* [HGM] can be >= 10 */
13674                 *p++ = '0' + emptycount;
13675             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13676             emptycount = 0;
13677         }
13678         *p++ = '/';
13679     }
13680     *(p - 1) = ' ';
13681
13682     /* [HGM] print Crazyhouse or Shogi holdings */
13683     if( gameInfo.holdingsWidth ) {
13684         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13685         q = p;
13686         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13687             piece = boards[move][i][BOARD_WIDTH-1];
13688             if( piece != EmptySquare )
13689               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13690                   *p++ = PieceToChar(piece);
13691         }
13692         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13693             piece = boards[move][BOARD_HEIGHT-i-1][0];
13694             if( piece != EmptySquare )
13695               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13696                   *p++ = PieceToChar(piece);
13697         }
13698
13699         if( q == p ) *p++ = '-';
13700         *p++ = ']';
13701         *p++ = ' ';
13702     }
13703
13704     /* Active color */
13705     *p++ = whiteToPlay ? 'w' : 'b';
13706     *p++ = ' ';
13707
13708   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13709     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13710   } else {
13711   if(nrCastlingRights) {
13712      q = p;
13713      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13714        /* [HGM] write directly from rights */
13715            if(boards[move][CASTLING][2] != NoRights &&
13716               boards[move][CASTLING][0] != NoRights   )
13717                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13718            if(boards[move][CASTLING][2] != NoRights &&
13719               boards[move][CASTLING][1] != NoRights   )
13720                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13721            if(boards[move][CASTLING][5] != NoRights &&
13722               boards[move][CASTLING][3] != NoRights   )
13723                 *p++ = boards[move][CASTLING][3] + AAA;
13724            if(boards[move][CASTLING][5] != NoRights &&
13725               boards[move][CASTLING][4] != NoRights   )
13726                 *p++ = boards[move][CASTLING][4] + AAA;
13727      } else {
13728
13729         /* [HGM] write true castling rights */
13730         if( nrCastlingRights == 6 ) {
13731             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13732                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13733             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13734                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13735             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13736                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13737             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13738                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13739         }
13740      }
13741      if (q == p) *p++ = '-'; /* No castling rights */
13742      *p++ = ' ';
13743   }
13744
13745   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13746      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13747     /* En passant target square */
13748     if (move > backwardMostMove) {
13749         fromX = moveList[move - 1][0] - AAA;
13750         fromY = moveList[move - 1][1] - ONE;
13751         toX = moveList[move - 1][2] - AAA;
13752         toY = moveList[move - 1][3] - ONE;
13753         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13754             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13755             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13756             fromX == toX) {
13757             /* 2-square pawn move just happened */
13758             *p++ = toX + AAA;
13759             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13760         } else {
13761             *p++ = '-';
13762         }
13763     } else if(move == backwardMostMove) {
13764         // [HGM] perhaps we should always do it like this, and forget the above?
13765         if((signed char)boards[move][EP_STATUS] >= 0) {
13766             *p++ = boards[move][EP_STATUS] + AAA;
13767             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13768         } else {
13769             *p++ = '-';
13770         }
13771     } else {
13772         *p++ = '-';
13773     }
13774     *p++ = ' ';
13775   }
13776   }
13777
13778     /* [HGM] find reversible plies */
13779     {   int i = 0, j=move;
13780
13781         if (appData.debugMode) { int k;
13782             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13783             for(k=backwardMostMove; k<=forwardMostMove; k++)
13784                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13785
13786         }
13787
13788         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13789         if( j == backwardMostMove ) i += initialRulePlies;
13790         sprintf(p, "%d ", i);
13791         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13792     }
13793     /* Fullmove number */
13794     sprintf(p, "%d", (move / 2) + 1);
13795     
13796     return StrSave(buf);
13797 }
13798
13799 Boolean
13800 ParseFEN(board, blackPlaysFirst, fen)
13801     Board board;
13802      int *blackPlaysFirst;
13803      char *fen;
13804 {
13805     int i, j;
13806     char *p;
13807     int emptycount;
13808     ChessSquare piece;
13809
13810     p = fen;
13811
13812     /* [HGM] by default clear Crazyhouse holdings, if present */
13813     if(gameInfo.holdingsWidth) {
13814        for(i=0; i<BOARD_HEIGHT; i++) {
13815            board[i][0]             = EmptySquare; /* black holdings */
13816            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13817            board[i][1]             = (ChessSquare) 0; /* black counts */
13818            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13819        }
13820     }
13821
13822     /* Piece placement data */
13823     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13824         j = 0;
13825         for (;;) {
13826             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13827                 if (*p == '/') p++;
13828                 emptycount = gameInfo.boardWidth - j;
13829                 while (emptycount--)
13830                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13831                 break;
13832 #if(BOARD_FILES >= 10)
13833             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13834                 p++; emptycount=10;
13835                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13836                 while (emptycount--)
13837                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13838 #endif
13839             } else if (isdigit(*p)) {
13840                 emptycount = *p++ - '0';
13841                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13842                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13843                 while (emptycount--)
13844                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13845             } else if (*p == '+' || isalpha(*p)) {
13846                 if (j >= gameInfo.boardWidth) return FALSE;
13847                 if(*p=='+') {
13848                     piece = CharToPiece(*++p);
13849                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13850                     piece = (ChessSquare) (PROMOTED piece ); p++;
13851                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13852                 } else piece = CharToPiece(*p++);
13853
13854                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13855                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13856                     piece = (ChessSquare) (PROMOTED piece);
13857                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13858                     p++;
13859                 }
13860                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13861             } else {
13862                 return FALSE;
13863             }
13864         }
13865     }
13866     while (*p == '/' || *p == ' ') p++;
13867
13868     /* [HGM] look for Crazyhouse holdings here */
13869     while(*p==' ') p++;
13870     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13871         if(*p == '[') p++;
13872         if(*p == '-' ) *p++; /* empty holdings */ else {
13873             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13874             /* if we would allow FEN reading to set board size, we would   */
13875             /* have to add holdings and shift the board read so far here   */
13876             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13877                 *p++;
13878                 if((int) piece >= (int) BlackPawn ) {
13879                     i = (int)piece - (int)BlackPawn;
13880                     i = PieceToNumber((ChessSquare)i);
13881                     if( i >= gameInfo.holdingsSize ) return FALSE;
13882                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13883                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13884                 } else {
13885                     i = (int)piece - (int)WhitePawn;
13886                     i = PieceToNumber((ChessSquare)i);
13887                     if( i >= gameInfo.holdingsSize ) return FALSE;
13888                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13889                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13890                 }
13891             }
13892         }
13893         if(*p == ']') *p++;
13894     }
13895
13896     while(*p == ' ') p++;
13897
13898     /* Active color */
13899     switch (*p++) {
13900       case 'w':
13901         *blackPlaysFirst = FALSE;
13902         break;
13903       case 'b': 
13904         *blackPlaysFirst = TRUE;
13905         break;
13906       default:
13907         return FALSE;
13908     }
13909
13910     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13911     /* return the extra info in global variiables             */
13912
13913     /* set defaults in case FEN is incomplete */
13914     board[EP_STATUS] = EP_UNKNOWN;
13915     for(i=0; i<nrCastlingRights; i++ ) {
13916         board[CASTLING][i] =
13917             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13918     }   /* assume possible unless obviously impossible */
13919     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13920     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13921     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13922     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13923     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13924     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13925     FENrulePlies = 0;
13926
13927     while(*p==' ') p++;
13928     if(nrCastlingRights) {
13929       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13930           /* castling indicator present, so default becomes no castlings */
13931           for(i=0; i<nrCastlingRights; i++ ) {
13932                  board[CASTLING][i] = NoRights;
13933           }
13934       }
13935       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13936              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13937              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13938              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13939         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13940
13941         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13942             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13943             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13944         }
13945         switch(c) {
13946           case'K':
13947               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13948               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13949               board[CASTLING][2] = whiteKingFile;
13950               break;
13951           case'Q':
13952               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13953               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13954               board[CASTLING][2] = whiteKingFile;
13955               break;
13956           case'k':
13957               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13958               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13959               board[CASTLING][5] = blackKingFile;
13960               break;
13961           case'q':
13962               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13963               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13964               board[CASTLING][5] = blackKingFile;
13965           case '-':
13966               break;
13967           default: /* FRC castlings */
13968               if(c >= 'a') { /* black rights */
13969                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13970                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13971                   if(i == BOARD_RGHT) break;
13972                   board[CASTLING][5] = i;
13973                   c -= AAA;
13974                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13975                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13976                   if(c > i)
13977                       board[CASTLING][3] = c;
13978                   else
13979                       board[CASTLING][4] = c;
13980               } else { /* white rights */
13981                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13982                     if(board[0][i] == WhiteKing) break;
13983                   if(i == BOARD_RGHT) break;
13984                   board[CASTLING][2] = i;
13985                   c -= AAA - 'a' + 'A';
13986                   if(board[0][c] >= WhiteKing) break;
13987                   if(c > i)
13988                       board[CASTLING][0] = c;
13989                   else
13990                       board[CASTLING][1] = c;
13991               }
13992         }
13993       }
13994     if (appData.debugMode) {
13995         fprintf(debugFP, "FEN castling rights:");
13996         for(i=0; i<nrCastlingRights; i++)
13997         fprintf(debugFP, " %d", board[CASTLING][i]);
13998         fprintf(debugFP, "\n");
13999     }
14000
14001       while(*p==' ') p++;
14002     }
14003
14004     /* read e.p. field in games that know e.p. capture */
14005     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14006        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14007       if(*p=='-') {
14008         p++; board[EP_STATUS] = EP_NONE;
14009       } else {
14010          char c = *p++ - AAA;
14011
14012          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14013          if(*p >= '0' && *p <='9') *p++;
14014          board[EP_STATUS] = c;
14015       }
14016     }
14017
14018
14019     if(sscanf(p, "%d", &i) == 1) {
14020         FENrulePlies = i; /* 50-move ply counter */
14021         /* (The move number is still ignored)    */
14022     }
14023
14024     return TRUE;
14025 }
14026       
14027 void
14028 EditPositionPasteFEN(char *fen)
14029 {
14030   if (fen != NULL) {
14031     Board initial_position;
14032
14033     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14034       DisplayError(_("Bad FEN position in clipboard"), 0);
14035       return ;
14036     } else {
14037       int savedBlackPlaysFirst = blackPlaysFirst;
14038       EditPositionEvent();
14039       blackPlaysFirst = savedBlackPlaysFirst;
14040       CopyBoard(boards[0], initial_position);
14041       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14042       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14043       DisplayBothClocks();
14044       DrawPosition(FALSE, boards[currentMove]);
14045     }
14046   }
14047 }
14048
14049 static char cseq[12] = "\\   ";
14050
14051 Boolean set_cont_sequence(char *new_seq)
14052 {
14053     int len;
14054     Boolean ret;
14055
14056     // handle bad attempts to set the sequence
14057         if (!new_seq)
14058                 return 0; // acceptable error - no debug
14059
14060     len = strlen(new_seq);
14061     ret = (len > 0) && (len < sizeof(cseq));
14062     if (ret)
14063         strcpy(cseq, new_seq);
14064     else if (appData.debugMode)
14065         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14066     return ret;
14067 }
14068
14069 /*
14070     reformat a source message so words don't cross the width boundary.  internal
14071     newlines are not removed.  returns the wrapped size (no null character unless
14072     included in source message).  If dest is NULL, only calculate the size required
14073     for the dest buffer.  lp argument indicats line position upon entry, and it's
14074     passed back upon exit.
14075 */
14076 int wrap(char *dest, char *src, int count, int width, int *lp)
14077 {
14078     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14079
14080     cseq_len = strlen(cseq);
14081     old_line = line = *lp;
14082     ansi = len = clen = 0;
14083
14084     for (i=0; i < count; i++)
14085     {
14086         if (src[i] == '\033')
14087             ansi = 1;
14088
14089         // if we hit the width, back up
14090         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14091         {
14092             // store i & len in case the word is too long
14093             old_i = i, old_len = len;
14094
14095             // find the end of the last word
14096             while (i && src[i] != ' ' && src[i] != '\n')
14097             {
14098                 i--;
14099                 len--;
14100             }
14101
14102             // word too long?  restore i & len before splitting it
14103             if ((old_i-i+clen) >= width)
14104             {
14105                 i = old_i;
14106                 len = old_len;
14107             }
14108
14109             // extra space?
14110             if (i && src[i-1] == ' ')
14111                 len--;
14112
14113             if (src[i] != ' ' && src[i] != '\n')
14114             {
14115                 i--;
14116                 if (len)
14117                     len--;
14118             }
14119
14120             // now append the newline and continuation sequence
14121             if (dest)
14122                 dest[len] = '\n';
14123             len++;
14124             if (dest)
14125                 strncpy(dest+len, cseq, cseq_len);
14126             len += cseq_len;
14127             line = cseq_len;
14128             clen = cseq_len;
14129             continue;
14130         }
14131
14132         if (dest)
14133             dest[len] = src[i];
14134         len++;
14135         if (!ansi)
14136             line++;
14137         if (src[i] == '\n')
14138             line = 0;
14139         if (src[i] == 'm')
14140             ansi = 0;
14141     }
14142     if (dest && appData.debugMode)
14143     {
14144         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14145             count, width, line, len, *lp);
14146         show_bytes(debugFP, src, count);
14147         fprintf(debugFP, "\ndest: ");
14148         show_bytes(debugFP, dest, len);
14149         fprintf(debugFP, "\n");
14150     }
14151     *lp = dest ? line : old_line;
14152
14153     return len;
14154 }
14155
14156 // [HGM] vari: routines for shelving variations
14157
14158 void 
14159 PushTail(int firstMove, int lastMove)
14160 {
14161         int i, j, nrMoves = lastMove - firstMove;
14162
14163         if(appData.icsActive) { // only in local mode
14164                 forwardMostMove = currentMove; // mimic old ICS behavior
14165                 return;
14166         }
14167         if(storedGames >= MAX_VARIATIONS-1) return;
14168
14169         // push current tail of game on stack
14170         savedResult[storedGames] = gameInfo.result;
14171         savedDetails[storedGames] = gameInfo.resultDetails;
14172         gameInfo.resultDetails = NULL;
14173         savedFirst[storedGames] = firstMove;
14174         savedLast [storedGames] = lastMove;
14175         savedFramePtr[storedGames] = framePtr;
14176         framePtr -= nrMoves; // reserve space for the boards
14177         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14178             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14179             for(j=0; j<MOVE_LEN; j++)
14180                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14181             for(j=0; j<2*MOVE_LEN; j++)
14182                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14183             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14184             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14185             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14186             pvInfoList[firstMove+i-1].depth = 0;
14187             commentList[framePtr+i] = commentList[firstMove+i];
14188             commentList[firstMove+i] = NULL;
14189         }
14190
14191         storedGames++;
14192         forwardMostMove = currentMove; // truncte game so we can start variation
14193         if(storedGames == 1) GreyRevert(FALSE);
14194 }
14195
14196 Boolean
14197 PopTail(Boolean annotate)
14198 {
14199         int i, j, nrMoves;
14200         char buf[8000], moveBuf[20];
14201
14202         if(appData.icsActive) return FALSE; // only in local mode
14203         if(!storedGames) return FALSE; // sanity
14204
14205         storedGames--;
14206         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14207         nrMoves = savedLast[storedGames] - currentMove;
14208         if(annotate) {
14209                 int cnt = 10;
14210                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14211                 else strcpy(buf, "(");
14212                 for(i=currentMove; i<forwardMostMove; i++) {
14213                         if(WhiteOnMove(i))
14214                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14215                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14216                         strcat(buf, moveBuf);
14217                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14218                 }
14219                 strcat(buf, ")");
14220         }
14221         for(i=1; i<nrMoves; i++) { // copy last variation back
14222             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14223             for(j=0; j<MOVE_LEN; j++)
14224                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14225             for(j=0; j<2*MOVE_LEN; j++)
14226                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14227             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14228             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14229             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14230             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14231             commentList[currentMove+i] = commentList[framePtr+i];
14232             commentList[framePtr+i] = NULL;
14233         }
14234         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14235         framePtr = savedFramePtr[storedGames];
14236         gameInfo.result = savedResult[storedGames];
14237         if(gameInfo.resultDetails != NULL) {
14238             free(gameInfo.resultDetails);
14239       }
14240         gameInfo.resultDetails = savedDetails[storedGames];
14241         forwardMostMove = currentMove + nrMoves;
14242         if(storedGames == 0) GreyRevert(TRUE);
14243         return TRUE;
14244 }
14245
14246 void 
14247 CleanupTail()
14248 {       // remove all shelved variations
14249         int i;
14250         for(i=0; i<storedGames; i++) {
14251             if(savedDetails[i])
14252                 free(savedDetails[i]);
14253             savedDetails[i] = NULL;
14254         }
14255         for(i=framePtr; i<MAX_MOVES; i++) {
14256                 if(commentList[i]) free(commentList[i]);
14257                 commentList[i] = NULL;
14258         }
14259         framePtr = MAX_MOVES-1;
14260         storedGames = 0;
14261 }