Merge branch 'master-20091209' into gtk
[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    if (appData.debugMode) {
1973      fprintf(debugFP, "Switch board from %s to %s\n",
1974              VariantName(gameInfo.variant), VariantName(newVariant));
1975      setbuf(debugFP, NULL);
1976    }
1977    shuffleOpenings = 0;       /* [HGM] shuffle */
1978    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1979    switch(newVariant) 
1980      {
1981      case VariantShogi:
1982        newWidth = 9;  newHeight = 9;
1983        gameInfo.holdingsSize = 7;
1984      case VariantBughouse:
1985      case VariantCrazyhouse:
1986        newHoldingsWidth = 2; break;
1987      case VariantGreat:
1988        newWidth = 10;
1989      case VariantSuper:
1990        newHoldingsWidth = 2;
1991        gameInfo.holdingsSize = 8;
1992        break;
1993      case VariantGothic:
1994      case VariantCapablanca:
1995      case VariantCapaRandom:
1996        newWidth = 10;
1997      default:
1998        newHoldingsWidth = gameInfo.holdingsSize = 0;
1999      };
2000    
2001    if(newWidth  != gameInfo.boardWidth  ||
2002       newHeight != gameInfo.boardHeight ||
2003       newHoldingsWidth != gameInfo.holdingsWidth ) {
2004      
2005      /* shift position to new playing area, if needed */
2006      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2007        for(i=0; i<BOARD_HEIGHT; i++) 
2008          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2009            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2010              board[i][j];
2011        for(i=0; i<newHeight; i++) {
2012          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2013          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2014        }
2015      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2016        for(i=0; i<BOARD_HEIGHT; i++)
2017          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2018            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2019              board[i][j];
2020      }
2021      gameInfo.boardWidth  = newWidth;
2022      gameInfo.boardHeight = newHeight;
2023      gameInfo.holdingsWidth = newHoldingsWidth;
2024      gameInfo.variant = newVariant;
2025      InitDrawingSizes(-2, 0);
2026    } else gameInfo.variant = newVariant;
2027    CopyBoard(oldBoard, board);   // remember correctly formatted board
2028      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2029    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2030 }
2031
2032 static int loggedOn = FALSE;
2033
2034 /*-- Game start info cache: --*/
2035 int gs_gamenum;
2036 char gs_kind[MSG_SIZ];
2037 static char player1Name[128] = "";
2038 static char player2Name[128] = "";
2039 static char cont_seq[] = "\n\\   ";
2040 static int player1Rating = -1;
2041 static int player2Rating = -1;
2042 /*----------------------------*/
2043
2044 ColorClass curColor = ColorNormal;
2045 int suppressKibitz = 0;
2046
2047 void
2048 read_from_ics(isr, closure, data, count, error)
2049      InputSourceRef isr;
2050      VOIDSTAR closure;
2051      char *data;
2052      int count;
2053      int error;
2054 {
2055 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2056 #define STARTED_NONE 0
2057 #define STARTED_MOVES 1
2058 #define STARTED_BOARD 2
2059 #define STARTED_OBSERVE 3
2060 #define STARTED_HOLDINGS 4
2061 #define STARTED_CHATTER 5
2062 #define STARTED_COMMENT 6
2063 #define STARTED_MOVES_NOHIDE 7
2064
2065     static int started = STARTED_NONE;
2066     static char parse[20000];
2067     static int parse_pos = 0;
2068     static char buf[BUF_SIZE + 1];
2069     static int firstTime = TRUE, intfSet = FALSE;
2070     static ColorClass prevColor = ColorNormal;
2071     static int savingComment = FALSE;
2072     static int cmatch = 0; // continuation sequence match
2073     char *bp;
2074     char str[500];
2075     int i, oldi;
2076     int buf_len;
2077     int next_out;
2078     int tkind;
2079     int backup;    /* [DM] For zippy color lines */
2080     char *p;
2081     char talker[MSG_SIZ]; // [HGM] chat
2082     int channel;
2083
2084     if (appData.debugMode) {
2085       if (!error) {
2086         fprintf(debugFP, "<ICS: ");
2087         show_bytes(debugFP, data, count);
2088         fprintf(debugFP, "\n");
2089       }
2090     }
2091
2092     if (appData.debugMode) { int f = forwardMostMove;
2093         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2094                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2095                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2096     }
2097     if (count > 0) {
2098         /* If last read ended with a partial line that we couldn't parse,
2099            prepend it to the new read and try again. */
2100         if (leftover_len > 0) {
2101             for (i=0; i<leftover_len; i++)
2102               buf[i] = buf[leftover_start + i];
2103         }
2104
2105     /* copy new characters into the buffer */
2106     bp = buf + leftover_len;
2107     buf_len=leftover_len;
2108     for (i=0; i<count; i++)
2109     {
2110         // ignore these
2111         if (data[i] == '\r')
2112             continue;
2113
2114         // join lines split by ICS?
2115         if (!appData.noJoin)
2116         {
2117             /*
2118                 Joining just consists of finding matches against the
2119                 continuation sequence, and discarding that sequence
2120                 if found instead of copying it.  So, until a match
2121                 fails, there's nothing to do since it might be the
2122                 complete sequence, and thus, something we don't want
2123                 copied.
2124             */
2125             if (data[i] == cont_seq[cmatch])
2126             {
2127                 cmatch++;
2128                 if (cmatch == strlen(cont_seq))
2129                 {
2130                     cmatch = 0; // complete match.  just reset the counter
2131
2132                     /*
2133                         it's possible for the ICS to not include the space
2134                         at the end of the last word, making our [correct]
2135                         join operation fuse two separate words.  the server
2136                         does this when the space occurs at the width setting.
2137                     */
2138                     if (!buf_len || buf[buf_len-1] != ' ')
2139                     {
2140                         *bp++ = ' ';
2141                         buf_len++;
2142                     }
2143                 }
2144                 continue;
2145             }
2146             else if (cmatch)
2147             {
2148                 /*
2149                     match failed, so we have to copy what matched before
2150                     falling through and copying this character.  In reality,
2151                     this will only ever be just the newline character, but
2152                     it doesn't hurt to be precise.
2153                 */
2154                 strncpy(bp, cont_seq, cmatch);
2155                 bp += cmatch;
2156                 buf_len += cmatch;
2157                 cmatch = 0;
2158             }
2159         }
2160
2161         // copy this char
2162         *bp++ = data[i];
2163         buf_len++;
2164     }
2165
2166         buf[buf_len] = NULLCHAR;
2167         next_out = leftover_len;
2168         leftover_start = 0;
2169
2170         i = 0;
2171         while (i < buf_len) {
2172             /* Deal with part of the TELNET option negotiation
2173                protocol.  We refuse to do anything beyond the
2174                defaults, except that we allow the WILL ECHO option,
2175                which ICS uses to turn off password echoing when we are
2176                directly connected to it.  We reject this option
2177                if localLineEditing mode is on (always on in xboard)
2178                and we are talking to port 23, which might be a real
2179                telnet server that will try to keep WILL ECHO on permanently.
2180              */
2181             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2182                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2183                 unsigned char option;
2184                 oldi = i;
2185                 switch ((unsigned char) buf[++i]) {
2186                   case TN_WILL:
2187                     if (appData.debugMode)
2188                       fprintf(debugFP, "\n<WILL ");
2189                     switch (option = (unsigned char) buf[++i]) {
2190                       case TN_ECHO:
2191                         if (appData.debugMode)
2192                           fprintf(debugFP, "ECHO ");
2193                         /* Reply only if this is a change, according
2194                            to the protocol rules. */
2195                         if (remoteEchoOption) break;
2196                         if (appData.localLineEditing &&
2197                             atoi(appData.icsPort) == TN_PORT) {
2198                             TelnetRequest(TN_DONT, TN_ECHO);
2199                         } else {
2200                             EchoOff();
2201                             TelnetRequest(TN_DO, TN_ECHO);
2202                             remoteEchoOption = TRUE;
2203                         }
2204                         break;
2205                       default:
2206                         if (appData.debugMode)
2207                           fprintf(debugFP, "%d ", option);
2208                         /* Whatever this is, we don't want it. */
2209                         TelnetRequest(TN_DONT, option);
2210                         break;
2211                     }
2212                     break;
2213                   case TN_WONT:
2214                     if (appData.debugMode)
2215                       fprintf(debugFP, "\n<WONT ");
2216                     switch (option = (unsigned char) buf[++i]) {
2217                       case TN_ECHO:
2218                         if (appData.debugMode)
2219                           fprintf(debugFP, "ECHO ");
2220                         /* Reply only if this is a change, according
2221                            to the protocol rules. */
2222                         if (!remoteEchoOption) break;
2223                         EchoOn();
2224                         TelnetRequest(TN_DONT, TN_ECHO);
2225                         remoteEchoOption = FALSE;
2226                         break;
2227                       default:
2228                         if (appData.debugMode)
2229                           fprintf(debugFP, "%d ", (unsigned char) option);
2230                         /* Whatever this is, it must already be turned
2231                            off, because we never agree to turn on
2232                            anything non-default, so according to the
2233                            protocol rules, we don't reply. */
2234                         break;
2235                     }
2236                     break;
2237                   case TN_DO:
2238                     if (appData.debugMode)
2239                       fprintf(debugFP, "\n<DO ");
2240                     switch (option = (unsigned char) buf[++i]) {
2241                       default:
2242                         /* Whatever this is, we refuse to do it. */
2243                         if (appData.debugMode)
2244                           fprintf(debugFP, "%d ", option);
2245                         TelnetRequest(TN_WONT, option);
2246                         break;
2247                     }
2248                     break;
2249                   case TN_DONT:
2250                     if (appData.debugMode)
2251                       fprintf(debugFP, "\n<DONT ");
2252                     switch (option = (unsigned char) buf[++i]) {
2253                       default:
2254                         if (appData.debugMode)
2255                           fprintf(debugFP, "%d ", option);
2256                         /* Whatever this is, we are already not doing
2257                            it, because we never agree to do anything
2258                            non-default, so according to the protocol
2259                            rules, we don't reply. */
2260                         break;
2261                     }
2262                     break;
2263                   case TN_IAC:
2264                     if (appData.debugMode)
2265                       fprintf(debugFP, "\n<IAC ");
2266                     /* Doubled IAC; pass it through */
2267                     i--;
2268                     break;
2269                   default:
2270                     if (appData.debugMode)
2271                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2272                     /* Drop all other telnet commands on the floor */
2273                     break;
2274                 }
2275                 if (oldi > next_out)
2276                   SendToPlayer(&buf[next_out], oldi - next_out);
2277                 if (++i > next_out)
2278                   next_out = i;
2279                 continue;
2280             }
2281
2282             /* OK, this at least will *usually* work */
2283             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2284                 loggedOn = TRUE;
2285             }
2286
2287             if (loggedOn && !intfSet) {
2288                 if (ics_type == ICS_ICC) {
2289                   sprintf(str,
2290                           "/set-quietly interface %s\n/set-quietly style 12\n",
2291                           programVersion);
2292                 } else if (ics_type == ICS_CHESSNET) {
2293                   sprintf(str, "/style 12\n");
2294                 } else {
2295                   strcpy(str, "alias $ @\n$set interface ");
2296                   strcat(str, programVersion);
2297                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2298 #ifdef WIN32
2299                   strcat(str, "$iset nohighlight 1\n");
2300 #endif
2301                   strcat(str, "$iset lock 1\n$style 12\n");
2302                 }
2303                 SendToICS(str);
2304                 NotifyFrontendLogin();
2305                 intfSet = TRUE;
2306             }
2307
2308             if (started == STARTED_COMMENT) {
2309                 /* Accumulate characters in comment */
2310                 parse[parse_pos++] = buf[i];
2311                 if (buf[i] == '\n') {
2312                     parse[parse_pos] = NULLCHAR;
2313                     if(chattingPartner>=0) {
2314                         char mess[MSG_SIZ];
2315                         sprintf(mess, "%s%s", talker, parse);
2316                         OutputChatMessage(chattingPartner, mess);
2317                         chattingPartner = -1;
2318                     } else
2319                     if(!suppressKibitz) // [HGM] kibitz
2320                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2321                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2322                         int nrDigit = 0, nrAlph = 0, i;
2323                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2324                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2325                         parse[parse_pos] = NULLCHAR;
2326                         // try to be smart: if it does not look like search info, it should go to
2327                         // ICS interaction window after all, not to engine-output window.
2328                         for(i=0; i<parse_pos; i++) { // count letters and digits
2329                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2330                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2331                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2332                         }
2333                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2334                             int depth=0; float score;
2335                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2336                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2337                                 pvInfoList[forwardMostMove-1].depth = depth;
2338                                 pvInfoList[forwardMostMove-1].score = 100*score;
2339                             }
2340                             OutputKibitz(suppressKibitz, parse);
2341                         } else {
2342                             char tmp[MSG_SIZ];
2343                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2344                             SendToPlayer(tmp, strlen(tmp));
2345                         }
2346                     }
2347                     started = STARTED_NONE;
2348                 } else {
2349                     /* Don't match patterns against characters in chatter */
2350                     i++;
2351                     continue;
2352                 }
2353             }
2354             if (started == STARTED_CHATTER) {
2355                 if (buf[i] != '\n') {
2356                     /* Don't match patterns against characters in chatter */
2357                     i++;
2358                     continue;
2359                 }
2360                 started = STARTED_NONE;
2361             }
2362
2363             /* Kludge to deal with rcmd protocol */
2364             if (firstTime && looking_at(buf, &i, "\001*")) {
2365                 DisplayFatalError(&buf[1], 0, 1);
2366                 continue;
2367             } else {
2368                 firstTime = FALSE;
2369             }
2370
2371             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2372                 ics_type = ICS_ICC;
2373                 ics_prefix = "/";
2374                 if (appData.debugMode)
2375                   fprintf(debugFP, "ics_type %d\n", ics_type);
2376                 continue;
2377             }
2378             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2379                 ics_type = ICS_FICS;
2380                 ics_prefix = "$";
2381                 if (appData.debugMode)
2382                   fprintf(debugFP, "ics_type %d\n", ics_type);
2383                 continue;
2384             }
2385             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2386                 ics_type = ICS_CHESSNET;
2387                 ics_prefix = "/";
2388                 if (appData.debugMode)
2389                   fprintf(debugFP, "ics_type %d\n", ics_type);
2390                 continue;
2391             }
2392
2393             if (!loggedOn &&
2394                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2395                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2396                  looking_at(buf, &i, "will be \"*\""))) {
2397               strcpy(ics_handle, star_match[0]);
2398               continue;
2399             }
2400
2401             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2402               char buf[MSG_SIZ];
2403               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2404               DisplayIcsInteractionTitle(buf);
2405               have_set_title = TRUE;
2406             }
2407
2408             /* skip finger notes */
2409             if (started == STARTED_NONE &&
2410                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2411                  (buf[i] == '1' && buf[i+1] == '0')) &&
2412                 buf[i+2] == ':' && buf[i+3] == ' ') {
2413               started = STARTED_CHATTER;
2414               i += 3;
2415               continue;
2416             }
2417
2418             /* skip formula vars */
2419             if (started == STARTED_NONE &&
2420                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2421               started = STARTED_CHATTER;
2422               i += 3;
2423               continue;
2424             }
2425
2426             oldi = i;
2427             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2428             if (appData.autoKibitz && started == STARTED_NONE &&
2429                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2430                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2431                 if(looking_at(buf, &i, "* kibitzes: ") &&
2432                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2433                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2434                         suppressKibitz = TRUE;
2435                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2436                                 && (gameMode == IcsPlayingWhite)) ||
2437                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2438                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2439                             started = STARTED_CHATTER; // own kibitz we simply discard
2440                         else {
2441                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2442                             parse_pos = 0; parse[0] = NULLCHAR;
2443                             savingComment = TRUE;
2444                             suppressKibitz = gameMode != IcsObserving ? 2 :
2445                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2446                         }
2447                         continue;
2448                 } else
2449                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2450                     started = STARTED_CHATTER;
2451                     suppressKibitz = TRUE;
2452                 }
2453             } // [HGM] kibitz: end of patch
2454
2455 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2456
2457             // [HGM] chat: intercept tells by users for which we have an open chat window
2458             channel = -1;
2459             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2460                                            looking_at(buf, &i, "* whispers:") ||
2461                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2462                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2463                 int p;
2464                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2465                 chattingPartner = -1;
2466
2467                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2468                 for(p=0; p<MAX_CHAT; p++) {
2469                     if(channel == atoi(chatPartner[p])) {
2470                     talker[0] = '['; strcat(talker, "]");
2471                     chattingPartner = p; break;
2472                     }
2473                 } else
2474                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2475                 for(p=0; p<MAX_CHAT; p++) {
2476                     if(!strcmp("WHISPER", chatPartner[p])) {
2477                         talker[0] = '['; strcat(talker, "]");
2478                         chattingPartner = p; break;
2479                     }
2480                 }
2481                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2482                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2483                     talker[0] = 0;
2484                     chattingPartner = p; break;
2485                 }
2486                 if(chattingPartner<0) i = oldi; else {
2487                     started = STARTED_COMMENT;
2488                     parse_pos = 0; parse[0] = NULLCHAR;
2489                     savingComment = TRUE;
2490                     suppressKibitz = TRUE;
2491                 }
2492             } // [HGM] chat: end of patch
2493
2494             if (appData.zippyTalk || appData.zippyPlay) {
2495                 /* [DM] Backup address for color zippy lines */
2496                 backup = i;
2497 #if ZIPPY
2498        #ifdef WIN32
2499                if (loggedOn == TRUE)
2500                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2501                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2502        #else
2503                 if (ZippyControl(buf, &i) ||
2504                     ZippyConverse(buf, &i) ||
2505                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2506                       loggedOn = TRUE;
2507                       if (!appData.colorize) continue;
2508                 }
2509        #endif
2510 #endif
2511             } // [DM] 'else { ' deleted
2512                 if (
2513                     /* Regular tells and says */
2514                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2515                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2516                     looking_at(buf, &i, "* says: ") ||
2517                     /* Don't color "message" or "messages" output */
2518                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2519                     looking_at(buf, &i, "*. * at *:*: ") ||
2520                     looking_at(buf, &i, "--* (*:*): ") ||
2521                     /* Message notifications (same color as tells) */
2522                     looking_at(buf, &i, "* has left a message ") ||
2523                     looking_at(buf, &i, "* just sent you a message:\n") ||
2524                     /* Whispers and kibitzes */
2525                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2526                     looking_at(buf, &i, "* kibitzes: ") ||
2527                     /* Channel tells */
2528                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2529
2530                   if (tkind == 1 && strchr(star_match[0], ':')) {
2531                       /* Avoid "tells you:" spoofs in channels */
2532                      tkind = 3;
2533                   }
2534                   if (star_match[0][0] == NULLCHAR ||
2535                       strchr(star_match[0], ' ') ||
2536                       (tkind == 3 && strchr(star_match[1], ' '))) {
2537                     /* Reject bogus matches */
2538                     i = oldi;
2539                   } else {
2540                     if (appData.colorize) {
2541                       if (oldi > next_out) {
2542                         SendToPlayer(&buf[next_out], oldi - next_out);
2543                         next_out = oldi;
2544                       }
2545                       switch (tkind) {
2546                       case 1:
2547                         Colorize(ColorTell, FALSE);
2548                         curColor = ColorTell;
2549                         break;
2550                       case 2:
2551                         Colorize(ColorKibitz, FALSE);
2552                         curColor = ColorKibitz;
2553                         break;
2554                       case 3:
2555                         p = strrchr(star_match[1], '(');
2556                         if (p == NULL) {
2557                           p = star_match[1];
2558                         } else {
2559                           p++;
2560                         }
2561                         if (atoi(p) == 1) {
2562                           Colorize(ColorChannel1, FALSE);
2563                           curColor = ColorChannel1;
2564                         } else {
2565                           Colorize(ColorChannel, FALSE);
2566                           curColor = ColorChannel;
2567                         }
2568                         break;
2569                       case 5:
2570                         curColor = ColorNormal;
2571                         break;
2572                       }
2573                     }
2574                     if (started == STARTED_NONE && appData.autoComment &&
2575                         (gameMode == IcsObserving ||
2576                          gameMode == IcsPlayingWhite ||
2577                          gameMode == IcsPlayingBlack)) {
2578                       parse_pos = i - oldi;
2579                       memcpy(parse, &buf[oldi], parse_pos);
2580                       parse[parse_pos] = NULLCHAR;
2581                       started = STARTED_COMMENT;
2582                       savingComment = TRUE;
2583                     } else {
2584                       started = STARTED_CHATTER;
2585                       savingComment = FALSE;
2586                     }
2587                     loggedOn = TRUE;
2588                     continue;
2589                   }
2590                 }
2591
2592                 if (looking_at(buf, &i, "* s-shouts: ") ||
2593                     looking_at(buf, &i, "* c-shouts: ")) {
2594                     if (appData.colorize) {
2595                         if (oldi > next_out) {
2596                             SendToPlayer(&buf[next_out], oldi - next_out);
2597                             next_out = oldi;
2598                         }
2599                         Colorize(ColorSShout, FALSE);
2600                         curColor = ColorSShout;
2601                     }
2602                     loggedOn = TRUE;
2603                     started = STARTED_CHATTER;
2604                     continue;
2605                 }
2606
2607                 if (looking_at(buf, &i, "--->")) {
2608                     loggedOn = TRUE;
2609                     continue;
2610                 }
2611
2612                 if (looking_at(buf, &i, "* shouts: ") ||
2613                     looking_at(buf, &i, "--> ")) {
2614                     if (appData.colorize) {
2615                         if (oldi > next_out) {
2616                             SendToPlayer(&buf[next_out], oldi - next_out);
2617                             next_out = oldi;
2618                         }
2619                         Colorize(ColorShout, FALSE);
2620                         curColor = ColorShout;
2621                     }
2622                     loggedOn = TRUE;
2623                     started = STARTED_CHATTER;
2624                     continue;
2625                 }
2626
2627                 if (looking_at( buf, &i, "Challenge:")) {
2628                     if (appData.colorize) {
2629                         if (oldi > next_out) {
2630                             SendToPlayer(&buf[next_out], oldi - next_out);
2631                             next_out = oldi;
2632                         }
2633                         Colorize(ColorChallenge, FALSE);
2634                         curColor = ColorChallenge;
2635                     }
2636                     loggedOn = TRUE;
2637                     continue;
2638                 }
2639
2640                 if (looking_at(buf, &i, "* offers you") ||
2641                     looking_at(buf, &i, "* offers to be") ||
2642                     looking_at(buf, &i, "* would like to") ||
2643                     looking_at(buf, &i, "* requests to") ||
2644                     looking_at(buf, &i, "Your opponent offers") ||
2645                     looking_at(buf, &i, "Your opponent requests")) {
2646
2647                     if (appData.colorize) {
2648                         if (oldi > next_out) {
2649                             SendToPlayer(&buf[next_out], oldi - next_out);
2650                             next_out = oldi;
2651                         }
2652                         Colorize(ColorRequest, FALSE);
2653                         curColor = ColorRequest;
2654                     }
2655                     continue;
2656                 }
2657
2658                 if (looking_at(buf, &i, "* (*) seeking")) {
2659                     if (appData.colorize) {
2660                         if (oldi > next_out) {
2661                             SendToPlayer(&buf[next_out], oldi - next_out);
2662                             next_out = oldi;
2663                         }
2664                         Colorize(ColorSeek, FALSE);
2665                         curColor = ColorSeek;
2666                     }
2667                     continue;
2668             }
2669
2670             if (looking_at(buf, &i, "\\   ")) {
2671                 if (prevColor != ColorNormal) {
2672                     if (oldi > next_out) {
2673                         SendToPlayer(&buf[next_out], oldi - next_out);
2674                         next_out = oldi;
2675                     }
2676                     Colorize(prevColor, TRUE);
2677                     curColor = prevColor;
2678                 }
2679                 if (savingComment) {
2680                     parse_pos = i - oldi;
2681                     memcpy(parse, &buf[oldi], parse_pos);
2682                     parse[parse_pos] = NULLCHAR;
2683                     started = STARTED_COMMENT;
2684                 } else {
2685                     started = STARTED_CHATTER;
2686                 }
2687                 continue;
2688             }
2689
2690             if (looking_at(buf, &i, "Black Strength :") ||
2691                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2692                 looking_at(buf, &i, "<10>") ||
2693                 looking_at(buf, &i, "#@#")) {
2694                 /* Wrong board style */
2695                 loggedOn = TRUE;
2696                 SendToICS(ics_prefix);
2697                 SendToICS("set style 12\n");
2698                 SendToICS(ics_prefix);
2699                 SendToICS("refresh\n");
2700                 continue;
2701             }
2702
2703             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2704                 ICSInitScript();
2705                 have_sent_ICS_logon = 1;
2706                 continue;
2707             }
2708
2709             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2710                 (looking_at(buf, &i, "\n<12> ") ||
2711                  looking_at(buf, &i, "<12> "))) {
2712                 loggedOn = TRUE;
2713                 if (oldi > next_out) {
2714                     SendToPlayer(&buf[next_out], oldi - next_out);
2715                 }
2716                 next_out = i;
2717                 started = STARTED_BOARD;
2718                 parse_pos = 0;
2719                 continue;
2720             }
2721
2722             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2723                 looking_at(buf, &i, "<b1> ")) {
2724                 if (oldi > next_out) {
2725                     SendToPlayer(&buf[next_out], oldi - next_out);
2726                 }
2727                 next_out = i;
2728                 started = STARTED_HOLDINGS;
2729                 parse_pos = 0;
2730                 continue;
2731             }
2732
2733             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2734                 loggedOn = TRUE;
2735                 /* Header for a move list -- first line */
2736
2737                 switch (ics_getting_history) {
2738                   case H_FALSE:
2739                     switch (gameMode) {
2740                       case IcsIdle:
2741                       case BeginningOfGame:
2742                         /* User typed "moves" or "oldmoves" while we
2743                            were idle.  Pretend we asked for these
2744                            moves and soak them up so user can step
2745                            through them and/or save them.
2746                            */
2747                         Reset(FALSE, TRUE);
2748                         gameMode = IcsObserving;
2749                         ModeHighlight();
2750                         ics_gamenum = -1;
2751                         ics_getting_history = H_GOT_UNREQ_HEADER;
2752                         break;
2753                       case EditGame: /*?*/
2754                       case EditPosition: /*?*/
2755                         /* Should above feature work in these modes too? */
2756                         /* For now it doesn't */
2757                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2758                         break;
2759                       default:
2760                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2761                         break;
2762                     }
2763                     break;
2764                   case H_REQUESTED:
2765                     /* Is this the right one? */
2766                     if (gameInfo.white && gameInfo.black &&
2767                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2768                         strcmp(gameInfo.black, star_match[2]) == 0) {
2769                         /* All is well */
2770                         ics_getting_history = H_GOT_REQ_HEADER;
2771                     }
2772                     break;
2773                   case H_GOT_REQ_HEADER:
2774                   case H_GOT_UNREQ_HEADER:
2775                   case H_GOT_UNWANTED_HEADER:
2776                   case H_GETTING_MOVES:
2777                     /* Should not happen */
2778                     DisplayError(_("Error gathering move list: two headers"), 0);
2779                     ics_getting_history = H_FALSE;
2780                     break;
2781                 }
2782
2783                 /* Save player ratings into gameInfo if needed */
2784                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2785                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2786                     (gameInfo.whiteRating == -1 ||
2787                      gameInfo.blackRating == -1)) {
2788
2789                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2790                     gameInfo.blackRating = string_to_rating(star_match[3]);
2791                     if (appData.debugMode)
2792                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2793                               gameInfo.whiteRating, gameInfo.blackRating);
2794                 }
2795                 continue;
2796             }
2797
2798             if (looking_at(buf, &i,
2799               "* * match, initial time: * minute*, increment: * second")) {
2800                 /* Header for a move list -- second line */
2801                 /* Initial board will follow if this is a wild game */
2802                 if (gameInfo.event != NULL) free(gameInfo.event);
2803                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2804                 gameInfo.event = StrSave(str);
2805                 /* [HGM] we switched variant. Translate boards if needed. */
2806                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2807                 continue;
2808             }
2809
2810             if (looking_at(buf, &i, "Move  ")) {
2811                 /* Beginning of a move list */
2812                 switch (ics_getting_history) {
2813                   case H_FALSE:
2814                     /* Normally should not happen */
2815                     /* Maybe user hit reset while we were parsing */
2816                     break;
2817                   case H_REQUESTED:
2818                     /* Happens if we are ignoring a move list that is not
2819                      * the one we just requested.  Common if the user
2820                      * tries to observe two games without turning off
2821                      * getMoveList */
2822                     break;
2823                   case H_GETTING_MOVES:
2824                     /* Should not happen */
2825                     DisplayError(_("Error gathering move list: nested"), 0);
2826                     ics_getting_history = H_FALSE;
2827                     break;
2828                   case H_GOT_REQ_HEADER:
2829                     ics_getting_history = H_GETTING_MOVES;
2830                     started = STARTED_MOVES;
2831                     parse_pos = 0;
2832                     if (oldi > next_out) {
2833                         SendToPlayer(&buf[next_out], oldi - next_out);
2834                     }
2835                     break;
2836                   case H_GOT_UNREQ_HEADER:
2837                     ics_getting_history = H_GETTING_MOVES;
2838                     started = STARTED_MOVES_NOHIDE;
2839                     parse_pos = 0;
2840                     break;
2841                   case H_GOT_UNWANTED_HEADER:
2842                     ics_getting_history = H_FALSE;
2843                     break;
2844                 }
2845                 continue;
2846             }
2847
2848             if (looking_at(buf, &i, "% ") ||
2849                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2850                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2851                 savingComment = FALSE;
2852                 switch (started) {
2853                   case STARTED_MOVES:
2854                   case STARTED_MOVES_NOHIDE:
2855                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2856                     parse[parse_pos + i - oldi] = NULLCHAR;
2857                     ParseGameHistory(parse);
2858 #if ZIPPY
2859                     if (appData.zippyPlay && first.initDone) {
2860                         FeedMovesToProgram(&first, forwardMostMove);
2861                         if (gameMode == IcsPlayingWhite) {
2862                             if (WhiteOnMove(forwardMostMove)) {
2863                                 if (first.sendTime) {
2864                                   if (first.useColors) {
2865                                     SendToProgram("black\n", &first);
2866                                   }
2867                                   SendTimeRemaining(&first, TRUE);
2868                                 }
2869                                 if (first.useColors) {
2870                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2871                                 }
2872                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2873                                 first.maybeThinking = TRUE;
2874                             } else {
2875                                 if (first.usePlayother) {
2876                                   if (first.sendTime) {
2877                                     SendTimeRemaining(&first, TRUE);
2878                                   }
2879                                   SendToProgram("playother\n", &first);
2880                                   firstMove = FALSE;
2881                                 } else {
2882                                   firstMove = TRUE;
2883                                 }
2884                             }
2885                         } else if (gameMode == IcsPlayingBlack) {
2886                             if (!WhiteOnMove(forwardMostMove)) {
2887                                 if (first.sendTime) {
2888                                   if (first.useColors) {
2889                                     SendToProgram("white\n", &first);
2890                                   }
2891                                   SendTimeRemaining(&first, FALSE);
2892                                 }
2893                                 if (first.useColors) {
2894                                   SendToProgram("black\n", &first);
2895                                 }
2896                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2897                                 first.maybeThinking = TRUE;
2898                             } else {
2899                                 if (first.usePlayother) {
2900                                   if (first.sendTime) {
2901                                     SendTimeRemaining(&first, FALSE);
2902                                   }
2903                                   SendToProgram("playother\n", &first);
2904                                   firstMove = FALSE;
2905                                 } else {
2906                                   firstMove = TRUE;
2907                                 }
2908                             }
2909                         }
2910                     }
2911 #endif
2912                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2913                         /* Moves came from oldmoves or moves command
2914                            while we weren't doing anything else.
2915                            */
2916                         currentMove = forwardMostMove;
2917                         ClearHighlights();/*!!could figure this out*/
2918                         flipView = appData.flipView;
2919                         DrawPosition(TRUE, boards[currentMove]);
2920                         DisplayBothClocks();
2921                         sprintf(str, "%s vs. %s",
2922                                 gameInfo.white, gameInfo.black);
2923                         DisplayTitle(str);
2924                         gameMode = IcsIdle;
2925                     } else {
2926                         /* Moves were history of an active game */
2927                         if (gameInfo.resultDetails != NULL) {
2928                             free(gameInfo.resultDetails);
2929                             gameInfo.resultDetails = NULL;
2930                         }
2931                     }
2932                     HistorySet(parseList, backwardMostMove,
2933                                forwardMostMove, currentMove-1);
2934                     DisplayMove(currentMove - 1);
2935                     if (started == STARTED_MOVES) next_out = i;
2936                     started = STARTED_NONE;
2937                     ics_getting_history = H_FALSE;
2938                     break;
2939
2940                   case STARTED_OBSERVE:
2941                     started = STARTED_NONE;
2942                     SendToICS(ics_prefix);
2943                     SendToICS("refresh\n");
2944                     break;
2945
2946                   default:
2947                     break;
2948                 }
2949                 if(bookHit) { // [HGM] book: simulate book reply
2950                     static char bookMove[MSG_SIZ]; // a bit generous?
2951
2952                     programStats.nodes = programStats.depth = programStats.time =
2953                     programStats.score = programStats.got_only_move = 0;
2954                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2955
2956                     strcpy(bookMove, "move ");
2957                     strcat(bookMove, bookHit);
2958                     HandleMachineMove(bookMove, &first);
2959                 }
2960                 continue;
2961             }
2962
2963             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2964                  started == STARTED_HOLDINGS ||
2965                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2966                 /* Accumulate characters in move list or board */
2967                 parse[parse_pos++] = buf[i];
2968             }
2969
2970             /* Start of game messages.  Mostly we detect start of game
2971                when the first board image arrives.  On some versions
2972                of the ICS, though, we need to do a "refresh" after starting
2973                to observe in order to get the current board right away. */
2974             if (looking_at(buf, &i, "Adding game * to observation list")) {
2975                 started = STARTED_OBSERVE;
2976                 continue;
2977             }
2978
2979             /* Handle auto-observe */
2980             if (appData.autoObserve &&
2981                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2982                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2983                 char *player;
2984                 /* Choose the player that was highlighted, if any. */
2985                 if (star_match[0][0] == '\033' ||
2986                     star_match[1][0] != '\033') {
2987                     player = star_match[0];
2988                 } else {
2989                     player = star_match[2];
2990                 }
2991                 sprintf(str, "%sobserve %s\n",
2992                         ics_prefix, StripHighlightAndTitle(player));
2993                 SendToICS(str);
2994
2995                 /* Save ratings from notify string */
2996                 strcpy(player1Name, star_match[0]);
2997                 player1Rating = string_to_rating(star_match[1]);
2998                 strcpy(player2Name, star_match[2]);
2999                 player2Rating = string_to_rating(star_match[3]);
3000
3001                 if (appData.debugMode)
3002                   fprintf(debugFP,
3003                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3004                           player1Name, player1Rating,
3005                           player2Name, player2Rating);
3006
3007                 continue;
3008             }
3009
3010             /* Deal with automatic examine mode after a game,
3011                and with IcsObserving -> IcsExamining transition */
3012             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3013                 looking_at(buf, &i, "has made you an examiner of game *")) {
3014
3015                 int gamenum = atoi(star_match[0]);
3016                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3017                     gamenum == ics_gamenum) {
3018                     /* We were already playing or observing this game;
3019                        no need to refetch history */
3020                     gameMode = IcsExamining;
3021                     if (pausing) {
3022                         pauseExamForwardMostMove = forwardMostMove;
3023                     } else if (currentMove < forwardMostMove) {
3024                         ForwardInner(forwardMostMove);
3025                     }
3026                 } else {
3027                     /* I don't think this case really can happen */
3028                     SendToICS(ics_prefix);
3029                     SendToICS("refresh\n");
3030                 }
3031                 continue;
3032             }
3033
3034             /* Error messages */
3035 //          if (ics_user_moved) {
3036             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3037                 if (looking_at(buf, &i, "Illegal move") ||
3038                     looking_at(buf, &i, "Not a legal move") ||
3039                     looking_at(buf, &i, "Your king is in check") ||
3040                     looking_at(buf, &i, "It isn't your turn") ||
3041                     looking_at(buf, &i, "It is not your move")) {
3042                     /* Illegal move */
3043                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3044                         currentMove = --forwardMostMove;
3045                         DisplayMove(currentMove - 1); /* before DMError */
3046                         DrawPosition(FALSE, boards[currentMove]);
3047                         SwitchClocks();
3048                         DisplayBothClocks();
3049                     }
3050                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3051                     ics_user_moved = 0;
3052                     continue;
3053                 }
3054             }
3055
3056             if (looking_at(buf, &i, "still have time") ||
3057                 looking_at(buf, &i, "not out of time") ||
3058                 looking_at(buf, &i, "either player is out of time") ||
3059                 looking_at(buf, &i, "has timeseal; checking")) {
3060                 /* We must have called his flag a little too soon */
3061                 whiteFlag = blackFlag = FALSE;
3062                 continue;
3063             }
3064
3065             if (looking_at(buf, &i, "added * seconds to") ||
3066                 looking_at(buf, &i, "seconds were added to")) {
3067                 /* Update the clocks */
3068                 SendToICS(ics_prefix);
3069                 SendToICS("refresh\n");
3070                 continue;
3071             }
3072
3073             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3074                 ics_clock_paused = TRUE;
3075                 StopClocks();
3076                 continue;
3077             }
3078
3079             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3080                 ics_clock_paused = FALSE;
3081                 StartClocks();
3082                 continue;
3083             }
3084
3085             /* Grab player ratings from the Creating: message.
3086                Note we have to check for the special case when
3087                the ICS inserts things like [white] or [black]. */
3088             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3089                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3090                 /* star_matches:
3091                    0    player 1 name (not necessarily white)
3092                    1    player 1 rating
3093                    2    empty, white, or black (IGNORED)
3094                    3    player 2 name (not necessarily black)
3095                    4    player 2 rating
3096
3097                    The names/ratings are sorted out when the game
3098                    actually starts (below).
3099                 */
3100                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3101                 player1Rating = string_to_rating(star_match[1]);
3102                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3103                 player2Rating = string_to_rating(star_match[4]);
3104
3105                 if (appData.debugMode)
3106                   fprintf(debugFP,
3107                           "Ratings from 'Creating:' %s %d, %s %d\n",
3108                           player1Name, player1Rating,
3109                           player2Name, player2Rating);
3110
3111                 continue;
3112             }
3113
3114             /* Improved generic start/end-of-game messages */
3115             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3116                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3117                 /* If tkind == 0: */
3118                 /* star_match[0] is the game number */
3119                 /*           [1] is the white player's name */
3120                 /*           [2] is the black player's name */
3121                 /* For end-of-game: */
3122                 /*           [3] is the reason for the game end */
3123                 /*           [4] is a PGN end game-token, preceded by " " */
3124                 /* For start-of-game: */
3125                 /*           [3] begins with "Creating" or "Continuing" */
3126                 /*           [4] is " *" or empty (don't care). */
3127                 int gamenum = atoi(star_match[0]);
3128                 char *whitename, *blackname, *why, *endtoken;
3129                 ChessMove endtype = (ChessMove) 0;
3130
3131                 if (tkind == 0) {
3132                   whitename = star_match[1];
3133                   blackname = star_match[2];
3134                   why = star_match[3];
3135                   endtoken = star_match[4];
3136                 } else {
3137                   whitename = star_match[1];
3138                   blackname = star_match[3];
3139                   why = star_match[5];
3140                   endtoken = star_match[6];
3141                 }
3142
3143                 /* Game start messages */
3144                 if (strncmp(why, "Creating ", 9) == 0 ||
3145                     strncmp(why, "Continuing ", 11) == 0) {
3146                     gs_gamenum = gamenum;
3147                     strcpy(gs_kind, strchr(why, ' ') + 1);
3148 #if ZIPPY
3149                     if (appData.zippyPlay) {
3150                         ZippyGameStart(whitename, blackname);
3151                     }
3152 #endif /*ZIPPY*/
3153                     continue;
3154                 }
3155
3156                 /* Game end messages */
3157                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3158                     ics_gamenum != gamenum) {
3159                     continue;
3160                 }
3161                 while (endtoken[0] == ' ') endtoken++;
3162                 switch (endtoken[0]) {
3163                   case '*':
3164                   default:
3165                     endtype = GameUnfinished;
3166                     break;
3167                   case '0':
3168                     endtype = BlackWins;
3169                     break;
3170                   case '1':
3171                     if (endtoken[1] == '/')
3172                       endtype = GameIsDrawn;
3173                     else
3174                       endtype = WhiteWins;
3175                     break;
3176                 }
3177                 GameEnds(endtype, why, GE_ICS);
3178 #if ZIPPY
3179                 if (appData.zippyPlay && first.initDone) {
3180                     ZippyGameEnd(endtype, why);
3181                     if (first.pr == NULL) {
3182                       /* Start the next process early so that we'll
3183                          be ready for the next challenge */
3184                       StartChessProgram(&first);
3185                     }
3186                     /* Send "new" early, in case this command takes
3187                        a long time to finish, so that we'll be ready
3188                        for the next challenge. */
3189                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3190                     Reset(TRUE, TRUE);
3191                 }
3192 #endif /*ZIPPY*/
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "Removing game * from observation") ||
3197                 looking_at(buf, &i, "no longer observing game *") ||
3198                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3199                 if (gameMode == IcsObserving &&
3200                     atoi(star_match[0]) == ics_gamenum)
3201                   {
3202                       /* icsEngineAnalyze */
3203                       if (appData.icsEngineAnalyze) {
3204                             ExitAnalyzeMode();
3205                             ModeHighlight();
3206                       }
3207                       StopClocks();
3208                       gameMode = IcsIdle;
3209                       ics_gamenum = -1;
3210                       ics_user_moved = FALSE;
3211                   }
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "no longer examining game *")) {
3216                 if (gameMode == IcsExamining &&
3217                     atoi(star_match[0]) == ics_gamenum)
3218                   {
3219                       gameMode = IcsIdle;
3220                       ics_gamenum = -1;
3221                       ics_user_moved = FALSE;
3222                   }
3223                 continue;
3224             }
3225
3226             /* Advance leftover_start past any newlines we find,
3227                so only partial lines can get reparsed */
3228             if (looking_at(buf, &i, "\n")) {
3229                 prevColor = curColor;
3230                 if (curColor != ColorNormal) {
3231                     if (oldi > next_out) {
3232                         SendToPlayer(&buf[next_out], oldi - next_out);
3233                         next_out = oldi;
3234                     }
3235                     Colorize(ColorNormal, FALSE);
3236                     curColor = ColorNormal;
3237                 }
3238                 if (started == STARTED_BOARD) {
3239                     started = STARTED_NONE;
3240                     parse[parse_pos] = NULLCHAR;
3241                     ParseBoard12(parse);
3242                     ics_user_moved = 0;
3243
3244                     /* Send premove here */
3245                     if (appData.premove) {
3246                       char str[MSG_SIZ];
3247                       if (currentMove == 0 &&
3248                           gameMode == IcsPlayingWhite &&
3249                           appData.premoveWhite) {
3250                         sprintf(str, "%s\n", appData.premoveWhiteText);
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                         SendToICS(str);
3254                       } else if (currentMove == 1 &&
3255                                  gameMode == IcsPlayingBlack &&
3256                                  appData.premoveBlack) {
3257                         sprintf(str, "%s\n", appData.premoveBlackText);
3258                         if (appData.debugMode)
3259                           fprintf(debugFP, "Sending premove:\n");
3260                         SendToICS(str);
3261                       } else if (gotPremove) {
3262                         gotPremove = 0;
3263                         ClearPremoveHighlights();
3264                         if (appData.debugMode)
3265                           fprintf(debugFP, "Sending premove:\n");
3266                           UserMoveEvent(premoveFromX, premoveFromY,
3267                                         premoveToX, premoveToY,
3268                                         premovePromoChar);
3269                       }
3270                     }
3271
3272                     /* Usually suppress following prompt */
3273                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3274                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3275                         if (looking_at(buf, &i, "*% ")) {
3276                             savingComment = FALSE;
3277                         }
3278                     }
3279                     next_out = i;
3280                 } else if (started == STARTED_HOLDINGS) {
3281                     int gamenum;
3282                     char new_piece[MSG_SIZ];
3283                     started = STARTED_NONE;
3284                     parse[parse_pos] = NULLCHAR;
3285                     if (appData.debugMode)
3286                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3287                                                         parse, currentMove);
3288                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3289                         gamenum == ics_gamenum) {
3290                         if (gameInfo.variant == VariantNormal) {
3291                           /* [HGM] We seem to switch variant during a game!
3292                            * Presumably no holdings were displayed, so we have
3293                            * to move the position two files to the right to
3294                            * create room for them!
3295                            */
3296                           VariantClass newVariant;
3297                           switch(gameInfo.boardWidth) { // base guess on board width
3298                                 case 9:  newVariant = VariantShogi; break;
3299                                 case 10: newVariant = VariantGreat; break;
3300                                 default: newVariant = VariantCrazyhouse; break;
3301                           }
3302                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3303                           /* Get a move list just to see the header, which
3304                              will tell us whether this is really bug or zh */
3305                           if (ics_getting_history == H_FALSE) {
3306                             ics_getting_history = H_REQUESTED;
3307                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3308                             SendToICS(str);
3309                           }
3310                         }
3311                         new_piece[0] = NULLCHAR;
3312                         sscanf(parse, "game %d white [%s black [%s <- %s",
3313                                &gamenum, white_holding, black_holding,
3314                                new_piece);
3315                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3316                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3317                         /* [HGM] copy holdings to board holdings area */
3318                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3319                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3320                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3321 #if ZIPPY
3322                         if (appData.zippyPlay && first.initDone) {
3323                             ZippyHoldings(white_holding, black_holding,
3324                                           new_piece);
3325                         }
3326 #endif /*ZIPPY*/
3327                         if (tinyLayout || smallLayout) {
3328                             char wh[16], bh[16];
3329                             PackHolding(wh, white_holding);
3330                             PackHolding(bh, black_holding);
3331                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3332                                     gameInfo.white, gameInfo.black);
3333                         } else {
3334                             sprintf(str, "%s [%s] vs. %s [%s]",
3335                                     gameInfo.white, white_holding,
3336                                     gameInfo.black, black_holding);
3337                         }
3338
3339                         DrawPosition(FALSE, boards[currentMove]);
3340                         DisplayTitle(str);
3341                     }
3342                     /* Suppress following prompt */
3343                     if (looking_at(buf, &i, "*% ")) {
3344                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3345                         savingComment = FALSE;
3346                     }
3347                     next_out = i;
3348                 }
3349                 continue;
3350             }
3351
3352             i++;                /* skip unparsed character and loop back */
3353         }
3354
3355         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3356             started != STARTED_HOLDINGS && i > next_out) {
3357             SendToPlayer(&buf[next_out], i - next_out);
3358             next_out = i;
3359         }
3360         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3361
3362         leftover_len = buf_len - leftover_start;
3363         /* if buffer ends with something we couldn't parse,
3364            reparse it after appending the next read */
3365
3366     } else if (count == 0) {
3367         RemoveInputSource(isr);
3368         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3369     } else {
3370         DisplayFatalError(_("Error reading from ICS"), error, 1);
3371     }
3372 }
3373
3374
3375 /* Board style 12 looks like this:
3376
3377    <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
3378
3379  * The "<12> " is stripped before it gets to this routine.  The two
3380  * trailing 0's (flip state and clock ticking) are later addition, and
3381  * some chess servers may not have them, or may have only the first.
3382  * Additional trailing fields may be added in the future.
3383  */
3384
3385 #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"
3386
3387 #define RELATION_OBSERVING_PLAYED    0
3388 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3389 #define RELATION_PLAYING_MYMOVE      1
3390 #define RELATION_PLAYING_NOTMYMOVE  -1
3391 #define RELATION_EXAMINING           2
3392 #define RELATION_ISOLATED_BOARD     -3
3393 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3394
3395 void
3396 ParseBoard12(string)
3397      char *string;
3398 {
3399     GameMode newGameMode;
3400     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3401     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3402     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3403     char to_play, board_chars[200];
3404     char move_str[500], str[500], elapsed_time[500];
3405     char black[32], white[32];
3406     Board board;
3407     int prevMove = currentMove;
3408     int ticking = 2;
3409     ChessMove moveType;
3410     int fromX, fromY, toX, toY;
3411     char promoChar;
3412     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3413     char *bookHit = NULL; // [HGM] book
3414     Boolean weird = FALSE, reqFlag = FALSE;
3415
3416     fromX = fromY = toX = toY = -1;
3417
3418     newGame = FALSE;
3419
3420     if (appData.debugMode)
3421       fprintf(debugFP, _("Parsing board: %s\n"), string);
3422
3423     move_str[0] = NULLCHAR;
3424     elapsed_time[0] = NULLCHAR;
3425     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3426         int  i = 0, j;
3427         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3428             if(string[i] == ' ') { ranks++; files = 0; }
3429             else files++;
3430             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3431             i++;
3432         }
3433         for(j = 0; j <i; j++) board_chars[j] = string[j];
3434         board_chars[i] = '\0';
3435         string += i + 1;
3436     }
3437     n = sscanf(string, PATTERN, &to_play, &double_push,
3438                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3439                &gamenum, white, black, &relation, &basetime, &increment,
3440                &white_stren, &black_stren, &white_time, &black_time,
3441                &moveNum, str, elapsed_time, move_str, &ics_flip,
3442                &ticking);
3443
3444     if (n < 21) {
3445         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3446         DisplayError(str, 0);
3447         return;
3448     }
3449
3450     /* Convert the move number to internal form */
3451     moveNum = (moveNum - 1) * 2;
3452     if (to_play == 'B') moveNum++;
3453     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3454       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3455                         0, 1);
3456       return;
3457     }
3458
3459     switch (relation) {
3460       case RELATION_OBSERVING_PLAYED:
3461       case RELATION_OBSERVING_STATIC:
3462         if (gamenum == -1) {
3463             /* Old ICC buglet */
3464             relation = RELATION_OBSERVING_STATIC;
3465         }
3466         newGameMode = IcsObserving;
3467         break;
3468       case RELATION_PLAYING_MYMOVE:
3469       case RELATION_PLAYING_NOTMYMOVE:
3470         newGameMode =
3471           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3472             IcsPlayingWhite : IcsPlayingBlack;
3473         break;
3474       case RELATION_EXAMINING:
3475         newGameMode = IcsExamining;
3476         break;
3477       case RELATION_ISOLATED_BOARD:
3478       default:
3479         /* Just display this board.  If user was doing something else,
3480            we will forget about it until the next board comes. */
3481         newGameMode = IcsIdle;
3482         break;
3483       case RELATION_STARTING_POSITION:
3484         newGameMode = gameMode;
3485         break;
3486     }
3487
3488     /* Modify behavior for initial board display on move listing
3489        of wild games.
3490        */
3491     switch (ics_getting_history) {
3492       case H_FALSE:
3493       case H_REQUESTED:
3494         break;
3495       case H_GOT_REQ_HEADER:
3496       case H_GOT_UNREQ_HEADER:
3497         /* This is the initial position of the current game */
3498         gamenum = ics_gamenum;
3499         moveNum = 0;            /* old ICS bug workaround */
3500         if (to_play == 'B') {
3501           startedFromSetupPosition = TRUE;
3502           blackPlaysFirst = TRUE;
3503           moveNum = 1;
3504           if (forwardMostMove == 0) forwardMostMove = 1;
3505           if (backwardMostMove == 0) backwardMostMove = 1;
3506           if (currentMove == 0) currentMove = 1;
3507         }
3508         newGameMode = gameMode;
3509         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3510         break;
3511       case H_GOT_UNWANTED_HEADER:
3512         /* This is an initial board that we don't want */
3513         return;
3514       case H_GETTING_MOVES:
3515         /* Should not happen */
3516         DisplayError(_("Error gathering move list: extra board"), 0);
3517         ics_getting_history = H_FALSE;
3518         return;
3519     }
3520
3521    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3522                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3523      /* [HGM] We seem to have switched variant unexpectedly
3524       * Try to guess new variant from board size
3525       */
3526           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3527           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3528           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3529           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3530           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3531           if(!weird) newVariant = VariantNormal;
3532           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3533           /* Get a move list just to see the header, which
3534              will tell us whether this is really bug or zh */
3535           if (ics_getting_history == H_FALSE) {
3536             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3537             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3538             SendToICS(str);
3539           }
3540     }
3541     
3542     /* Take action if this is the first board of a new game, or of a
3543        different game than is currently being displayed.  */
3544     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3545         relation == RELATION_ISOLATED_BOARD) {
3546
3547         /* Forget the old game and get the history (if any) of the new one */
3548         if (gameMode != BeginningOfGame) {
3549           Reset(TRUE, TRUE);
3550         }
3551         newGame = TRUE;
3552         if (appData.autoRaiseBoard) BoardToTop();
3553         prevMove = -3;
3554         if (gamenum == -1) {
3555             newGameMode = IcsIdle;
3556         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3557                    appData.getMoveList && !reqFlag) {
3558             /* Need to get game history */
3559             ics_getting_history = H_REQUESTED;
3560             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3561             SendToICS(str);
3562         }
3563
3564         /* Initially flip the board to have black on the bottom if playing
3565            black or if the ICS flip flag is set, but let the user change
3566            it with the Flip View button. */
3567         flipView = appData.autoFlipView ?
3568           (newGameMode == IcsPlayingBlack) || ics_flip :
3569           appData.flipView;
3570
3571         /* Done with values from previous mode; copy in new ones */
3572         gameMode = newGameMode;
3573         ModeHighlight();
3574         ics_gamenum = gamenum;
3575         if (gamenum == gs_gamenum) {
3576             int klen = strlen(gs_kind);
3577             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3578             sprintf(str, "ICS %s", gs_kind);
3579             gameInfo.event = StrSave(str);
3580         } else {
3581             gameInfo.event = StrSave("ICS game");
3582         }
3583         gameInfo.site = StrSave(appData.icsHost);
3584         gameInfo.date = PGNDate();
3585         gameInfo.round = StrSave("-");
3586         gameInfo.white = StrSave(white);
3587         gameInfo.black = StrSave(black);
3588         timeControl = basetime * 60 * 1000;
3589         timeControl_2 = 0;
3590         timeIncrement = increment * 1000;
3591         movesPerSession = 0;
3592         gameInfo.timeControl = TimeControlTagValue();
3593         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3594   if (appData.debugMode) {
3595     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3596     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3597     setbuf(debugFP, NULL);
3598   }
3599
3600         gameInfo.outOfBook = NULL;
3601
3602         /* Do we have the ratings? */
3603         if (strcmp(player1Name, white) == 0 &&
3604             strcmp(player2Name, black) == 0) {
3605             if (appData.debugMode)
3606               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3607                       player1Rating, player2Rating);
3608             gameInfo.whiteRating = player1Rating;
3609             gameInfo.blackRating = player2Rating;
3610         } else if (strcmp(player2Name, white) == 0 &&
3611                    strcmp(player1Name, black) == 0) {
3612             if (appData.debugMode)
3613               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3614                       player2Rating, player1Rating);
3615             gameInfo.whiteRating = player2Rating;
3616             gameInfo.blackRating = player1Rating;
3617         }
3618         player1Name[0] = player2Name[0] = NULLCHAR;
3619
3620         /* Silence shouts if requested */
3621         if (appData.quietPlay &&
3622             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3623             SendToICS(ics_prefix);
3624             SendToICS("set shout 0\n");
3625         }
3626     }
3627
3628     /* Deal with midgame name changes */
3629     if (!newGame) {
3630         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3631             if (gameInfo.white) free(gameInfo.white);
3632             gameInfo.white = StrSave(white);
3633         }
3634         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3635             if (gameInfo.black) free(gameInfo.black);
3636             gameInfo.black = StrSave(black);
3637         }
3638     }
3639
3640     /* Throw away game result if anything actually changes in examine mode */
3641     if (gameMode == IcsExamining && !newGame) {
3642         gameInfo.result = GameUnfinished;
3643         if (gameInfo.resultDetails != NULL) {
3644             free(gameInfo.resultDetails);
3645             gameInfo.resultDetails = NULL;
3646         }
3647     }
3648
3649     /* In pausing && IcsExamining mode, we ignore boards coming
3650        in if they are in a different variation than we are. */
3651     if (pauseExamInvalid) return;
3652     if (pausing && gameMode == IcsExamining) {
3653         if (moveNum <= pauseExamForwardMostMove) {
3654             pauseExamInvalid = TRUE;
3655             forwardMostMove = pauseExamForwardMostMove;
3656             return;
3657         }
3658     }
3659
3660   if (appData.debugMode) {
3661     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3662   }
3663     /* Parse the board */
3664     for (k = 0; k < ranks; k++) {
3665       for (j = 0; j < files; j++)
3666         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3667       if(gameInfo.holdingsWidth > 1) {
3668            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3669            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3670       }
3671     }
3672     CopyBoard(boards[moveNum], board);
3673     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3674     if (moveNum == 0) {
3675         startedFromSetupPosition =
3676           !CompareBoards(board, initialPosition);
3677         if(startedFromSetupPosition)
3678             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3679     }
3680
3681     /* [HGM] Set castling rights. Take the outermost Rooks,
3682        to make it also work for FRC opening positions. Note that board12
3683        is really defective for later FRC positions, as it has no way to
3684        indicate which Rook can castle if they are on the same side of King.
3685        For the initial position we grant rights to the outermost Rooks,
3686        and remember thos rights, and we then copy them on positions
3687        later in an FRC game. This means WB might not recognize castlings with
3688        Rooks that have moved back to their original position as illegal,
3689        but in ICS mode that is not its job anyway.
3690     */
3691     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3692     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3693
3694         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3695             if(board[0][i] == WhiteRook) j = i;
3696         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3697         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3698             if(board[0][i] == WhiteRook) j = i;
3699         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3700         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3701             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3702         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3703         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3706
3707         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3708         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3710         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3711             if(board[BOARD_HEIGHT-1][k] == bKing)
3712                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3713     } else { int r;
3714         r = boards[moveNum][CASTLING][0] = initialRights[0];
3715         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3716         r = boards[moveNum][CASTLING][1] = initialRights[1];
3717         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3718         r = boards[moveNum][CASTLING][3] = initialRights[3];
3719         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3720         r = boards[moveNum][CASTLING][4] = initialRights[4];
3721         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3722         /* wildcastle kludge: always assume King has rights */
3723         r = boards[moveNum][CASTLING][2] = initialRights[2];
3724         r = boards[moveNum][CASTLING][5] = initialRights[5];
3725     }
3726     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3727     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3728
3729
3730     if (ics_getting_history == H_GOT_REQ_HEADER ||
3731         ics_getting_history == H_GOT_UNREQ_HEADER) {
3732         /* This was an initial position from a move list, not
3733            the current position */
3734         return;
3735     }
3736
3737     /* Update currentMove and known move number limits */
3738     newMove = newGame || moveNum > forwardMostMove;
3739
3740     if (newGame) {
3741         forwardMostMove = backwardMostMove = currentMove = moveNum;
3742         if (gameMode == IcsExamining && moveNum == 0) {
3743           /* Workaround for ICS limitation: we are not told the wild
3744              type when starting to examine a game.  But if we ask for
3745              the move list, the move list header will tell us */
3746             ics_getting_history = H_REQUESTED;
3747             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3748             SendToICS(str);
3749         }
3750     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3751                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3752 #if ZIPPY
3753         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3754         /* [HGM] applied this also to an engine that is silently watching        */
3755         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3756             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3757             gameInfo.variant == currentlyInitializedVariant) {
3758           takeback = forwardMostMove - moveNum;
3759           for (i = 0; i < takeback; i++) {
3760             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3761             SendToProgram("undo\n", &first);
3762           }
3763         }
3764 #endif
3765
3766         forwardMostMove = moveNum;
3767         if (!pausing || currentMove > forwardMostMove)
3768           currentMove = forwardMostMove;
3769     } else {
3770         /* New part of history that is not contiguous with old part */
3771         if (pausing && gameMode == IcsExamining) {
3772             pauseExamInvalid = TRUE;
3773             forwardMostMove = pauseExamForwardMostMove;
3774             return;
3775         }
3776         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3777 #if ZIPPY
3778             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3779                 // [HGM] when we will receive the move list we now request, it will be
3780                 // fed to the engine from the first move on. So if the engine is not
3781                 // in the initial position now, bring it there.
3782                 InitChessProgram(&first, 0);
3783             }
3784 #endif
3785             ics_getting_history = H_REQUESTED;
3786             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3787             SendToICS(str);
3788         }
3789         forwardMostMove = backwardMostMove = currentMove = moveNum;
3790     }
3791
3792     /* Update the clocks */
3793     if (strchr(elapsed_time, '.')) {
3794       /* Time is in ms */
3795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3797     } else {
3798       /* Time is in seconds */
3799       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3800       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3801     }
3802
3803
3804 #if ZIPPY
3805     if (appData.zippyPlay && newGame &&
3806         gameMode != IcsObserving && gameMode != IcsIdle &&
3807         gameMode != IcsExamining)
3808       ZippyFirstBoard(moveNum, basetime, increment);
3809 #endif
3810
3811     /* Put the move on the move list, first converting
3812        to canonical algebraic form. */
3813     if (moveNum > 0) {
3814   if (appData.debugMode) {
3815     if (appData.debugMode) { int f = forwardMostMove;
3816         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3817                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3818                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3819     }
3820     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3821     fprintf(debugFP, "moveNum = %d\n", moveNum);
3822     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3823     setbuf(debugFP, NULL);
3824   }
3825         if (moveNum <= backwardMostMove) {
3826             /* We don't know what the board looked like before
3827                this move.  Punt. */
3828             strcpy(parseList[moveNum - 1], move_str);
3829             strcat(parseList[moveNum - 1], " ");
3830             strcat(parseList[moveNum - 1], elapsed_time);
3831             moveList[moveNum - 1][0] = NULLCHAR;
3832         } else if (strcmp(move_str, "none") == 0) {
3833             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3834             /* Again, we don't know what the board looked like;
3835                this is really the start of the game. */
3836             parseList[moveNum - 1][0] = NULLCHAR;
3837             moveList[moveNum - 1][0] = NULLCHAR;
3838             backwardMostMove = moveNum;
3839             startedFromSetupPosition = TRUE;
3840             fromX = fromY = toX = toY = -1;
3841         } else {
3842           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3843           //                 So we parse the long-algebraic move string in stead of the SAN move
3844           int valid; char buf[MSG_SIZ], *prom;
3845
3846           // str looks something like "Q/a1-a2"; kill the slash
3847           if(str[1] == '/')
3848                 sprintf(buf, "%c%s", str[0], str+2);
3849           else  strcpy(buf, str); // might be castling
3850           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3851                 strcat(buf, prom); // long move lacks promo specification!
3852           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3853                 if(appData.debugMode)
3854                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3855                 strcpy(move_str, buf);
3856           }
3857           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3858                                 &fromX, &fromY, &toX, &toY, &promoChar)
3859                || ParseOneMove(buf, moveNum - 1, &moveType,
3860                                 &fromX, &fromY, &toX, &toY, &promoChar);
3861           // end of long SAN patch
3862           if (valid) {
3863             (void) CoordsToAlgebraic(boards[moveNum - 1],
3864                                      PosFlags(moveNum - 1),
3865                                      fromY, fromX, toY, toX, promoChar,
3866                                      parseList[moveNum-1]);
3867             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3868               case MT_NONE:
3869               case MT_STALEMATE:
3870               default:
3871                 break;
3872               case MT_CHECK:
3873                 if(gameInfo.variant != VariantShogi)
3874                     strcat(parseList[moveNum - 1], "+");
3875                 break;
3876               case MT_CHECKMATE:
3877               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3878                 strcat(parseList[moveNum - 1], "#");
3879                 break;
3880             }
3881             strcat(parseList[moveNum - 1], " ");
3882             strcat(parseList[moveNum - 1], elapsed_time);
3883             /* currentMoveString is set as a side-effect of ParseOneMove */
3884             strcpy(moveList[moveNum - 1], currentMoveString);
3885             strcat(moveList[moveNum - 1], "\n");
3886           } else {
3887             /* Move from ICS was illegal!?  Punt. */
3888   if (appData.debugMode) {
3889     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3890     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3891   }
3892             strcpy(parseList[moveNum - 1], move_str);
3893             strcat(parseList[moveNum - 1], " ");
3894             strcat(parseList[moveNum - 1], elapsed_time);
3895             moveList[moveNum - 1][0] = NULLCHAR;
3896             fromX = fromY = toX = toY = -1;
3897           }
3898         }
3899   if (appData.debugMode) {
3900     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3901     setbuf(debugFP, NULL);
3902   }
3903
3904 #if ZIPPY
3905         /* Send move to chess program (BEFORE animating it). */
3906         if (appData.zippyPlay && !newGame && newMove &&
3907            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3908
3909             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3910                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3911                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3912                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3913                             move_str);
3914                     DisplayError(str, 0);
3915                 } else {
3916                     if (first.sendTime) {
3917                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3918                     }
3919                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3920                     if (firstMove && !bookHit) {
3921                         firstMove = FALSE;
3922                         if (first.useColors) {
3923                           SendToProgram(gameMode == IcsPlayingWhite ?
3924                                         "white\ngo\n" :
3925                                         "black\ngo\n", &first);
3926                         } else {
3927                           SendToProgram("go\n", &first);
3928                         }
3929                         first.maybeThinking = TRUE;
3930                     }
3931                 }
3932             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3933               if (moveList[moveNum - 1][0] == NULLCHAR) {
3934                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3935                 DisplayError(str, 0);
3936               } else {
3937                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3938                 SendMoveToProgram(moveNum - 1, &first);
3939               }
3940             }
3941         }
3942 #endif
3943     }
3944
3945     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3946         /* If move comes from a remote source, animate it.  If it
3947            isn't remote, it will have already been animated. */
3948         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3949             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3950         }
3951         if (!pausing && appData.highlightLastMove) {
3952             SetHighlights(fromX, fromY, toX, toY);
3953         }
3954     }
3955
3956     /* Start the clocks */
3957     whiteFlag = blackFlag = FALSE;
3958     appData.clockMode = !(basetime == 0 && increment == 0);
3959     if (ticking == 0) {
3960       ics_clock_paused = TRUE;
3961       StopClocks();
3962     } else if (ticking == 1) {
3963       ics_clock_paused = FALSE;
3964     }
3965     if (gameMode == IcsIdle ||
3966         relation == RELATION_OBSERVING_STATIC ||
3967         relation == RELATION_EXAMINING ||
3968         ics_clock_paused)
3969       DisplayBothClocks();
3970     else
3971       StartClocks();
3972
3973     /* Display opponents and material strengths */
3974     if (gameInfo.variant != VariantBughouse &&
3975         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3976         if (tinyLayout || smallLayout) {
3977             if(gameInfo.variant == VariantNormal)
3978                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3980                     basetime, increment);
3981             else
3982                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3984                     basetime, increment, (int) gameInfo.variant);
3985         } else {
3986             if(gameInfo.variant == VariantNormal)
3987                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3988                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3989                     basetime, increment);
3990             else
3991                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3992                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3993                     basetime, increment, VariantName(gameInfo.variant));
3994         }
3995         DisplayTitle(str);
3996   if (appData.debugMode) {
3997     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3998   }
3999     }
4000
4001
4002     /* Display the board */
4003     if (!pausing && !appData.noGUI) {
4004       if (appData.premove)
4005           if (!gotPremove ||
4006              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4007              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4008               ClearPremoveHighlights();
4009
4010       DrawPosition(FALSE, boards[currentMove]);
4011       DisplayMove(moveNum - 1);
4012       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4013             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4014               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4015         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4016       }
4017     }
4018
4019     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4020 #if ZIPPY
4021     if(bookHit) { // [HGM] book: simulate book reply
4022         static char bookMove[MSG_SIZ]; // a bit generous?
4023
4024         programStats.nodes = programStats.depth = programStats.time =
4025         programStats.score = programStats.got_only_move = 0;
4026         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4027
4028         strcpy(bookMove, "move ");
4029         strcat(bookMove, bookHit);
4030         HandleMachineMove(bookMove, &first);
4031     }
4032 #endif
4033 }
4034
4035 void
4036 GetMoveListEvent()
4037 {
4038     char buf[MSG_SIZ];
4039     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4040         ics_getting_history = H_REQUESTED;
4041         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4042         SendToICS(buf);
4043     }
4044 }
4045
4046 void
4047 AnalysisPeriodicEvent(force)
4048      int force;
4049 {
4050     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4051          && !force) || !appData.periodicUpdates)
4052       return;
4053
4054     /* Send . command to Crafty to collect stats */
4055     SendToProgram(".\n", &first);
4056
4057     /* Don't send another until we get a response (this makes
4058        us stop sending to old Crafty's which don't understand
4059        the "." command (sending illegal cmds resets node count & time,
4060        which looks bad)) */
4061     programStats.ok_to_send = 0;
4062 }
4063
4064 void ics_update_width(new_width)
4065         int new_width;
4066 {
4067         ics_printf("set width %d\n", new_width);
4068 }
4069
4070 void
4071 SendMoveToProgram(moveNum, cps)
4072      int moveNum;
4073      ChessProgramState *cps;
4074 {
4075     char buf[MSG_SIZ];
4076
4077     if (cps->useUsermove) {
4078       SendToProgram("usermove ", cps);
4079     }
4080     if (cps->useSAN) {
4081       char *space;
4082       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4083         int len = space - parseList[moveNum];
4084         memcpy(buf, parseList[moveNum], len);
4085         buf[len++] = '\n';
4086         buf[len] = NULLCHAR;
4087       } else {
4088         sprintf(buf, "%s\n", parseList[moveNum]);
4089       }
4090       SendToProgram(buf, cps);
4091     } else {
4092       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4093         AlphaRank(moveList[moveNum], 4);
4094         SendToProgram(moveList[moveNum], cps);
4095         AlphaRank(moveList[moveNum], 4); // and back
4096       } else
4097       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4098        * the engine. It would be nice to have a better way to identify castle
4099        * moves here. */
4100       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4101                                                                          && cps->useOOCastle) {
4102         int fromX = moveList[moveNum][0] - AAA;
4103         int fromY = moveList[moveNum][1] - ONE;
4104         int toX = moveList[moveNum][2] - AAA;
4105         int toY = moveList[moveNum][3] - ONE;
4106         if((boards[moveNum][fromY][fromX] == WhiteKing
4107             && boards[moveNum][toY][toX] == WhiteRook)
4108            || (boards[moveNum][fromY][fromX] == BlackKing
4109                && boards[moveNum][toY][toX] == BlackRook)) {
4110           if(toX > fromX) SendToProgram("O-O\n", cps);
4111           else SendToProgram("O-O-O\n", cps);
4112         }
4113         else SendToProgram(moveList[moveNum], cps);
4114       }
4115       else SendToProgram(moveList[moveNum], cps);
4116       /* End of additions by Tord */
4117     }
4118
4119     /* [HGM] setting up the opening has brought engine in force mode! */
4120     /*       Send 'go' if we are in a mode where machine should play. */
4121     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4122         (gameMode == TwoMachinesPlay   ||
4123 #ifdef ZIPPY
4124          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4125 #endif
4126          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4127         SendToProgram("go\n", cps);
4128   if (appData.debugMode) {
4129     fprintf(debugFP, "(extra)\n");
4130   }
4131     }
4132     setboardSpoiledMachineBlack = 0;
4133 }
4134
4135 void
4136 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4137      ChessMove moveType;
4138      int fromX, fromY, toX, toY;
4139 {
4140     char user_move[MSG_SIZ];
4141
4142     switch (moveType) {
4143       default:
4144         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4145                 (int)moveType, fromX, fromY, toX, toY);
4146         DisplayError(user_move + strlen("say "), 0);
4147         break;
4148       case WhiteKingSideCastle:
4149       case BlackKingSideCastle:
4150       case WhiteQueenSideCastleWild:
4151       case BlackQueenSideCastleWild:
4152       /* PUSH Fabien */
4153       case WhiteHSideCastleFR:
4154       case BlackHSideCastleFR:
4155       /* POP Fabien */
4156         sprintf(user_move, "o-o\n");
4157         break;
4158       case WhiteQueenSideCastle:
4159       case BlackQueenSideCastle:
4160       case WhiteKingSideCastleWild:
4161       case BlackKingSideCastleWild:
4162       /* PUSH Fabien */
4163       case WhiteASideCastleFR:
4164       case BlackASideCastleFR:
4165       /* POP Fabien */
4166         sprintf(user_move, "o-o-o\n");
4167         break;
4168       case WhitePromotionQueen:
4169       case BlackPromotionQueen:
4170       case WhitePromotionRook:
4171       case BlackPromotionRook:
4172       case WhitePromotionBishop:
4173       case BlackPromotionBishop:
4174       case WhitePromotionKnight:
4175       case BlackPromotionKnight:
4176       case WhitePromotionKing:
4177       case BlackPromotionKing:
4178       case WhitePromotionChancellor:
4179       case BlackPromotionChancellor:
4180       case WhitePromotionArchbishop:
4181       case BlackPromotionArchbishop:
4182         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4183             sprintf(user_move, "%c%c%c%c=%c\n",
4184                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4185                 PieceToChar(WhiteFerz));
4186         else if(gameInfo.variant == VariantGreat)
4187             sprintf(user_move, "%c%c%c%c=%c\n",
4188                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189                 PieceToChar(WhiteMan));
4190         else
4191             sprintf(user_move, "%c%c%c%c=%c\n",
4192                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193                 PieceToChar(PromoPiece(moveType)));
4194         break;
4195       case WhiteDrop:
4196       case BlackDrop:
4197         sprintf(user_move, "%c@%c%c\n",
4198                 ToUpper(PieceToChar((ChessSquare) fromX)),
4199                 AAA + toX, ONE + toY);
4200         break;
4201       case NormalMove:
4202       case WhiteCapturesEnPassant:
4203       case BlackCapturesEnPassant:
4204       case IllegalMove:  /* could be a variant we don't quite understand */
4205         sprintf(user_move, "%c%c%c%c\n",
4206                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4207         break;
4208     }
4209     SendToICS(user_move);
4210     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4211         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4212 }
4213
4214 void
4215 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4216      int rf, ff, rt, ft;
4217      char promoChar;
4218      char move[7];
4219 {
4220     if (rf == DROP_RANK) {
4221         sprintf(move, "%c@%c%c\n",
4222                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4223     } else {
4224         if (promoChar == 'x' || promoChar == NULLCHAR) {
4225             sprintf(move, "%c%c%c%c\n",
4226                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4227         } else {
4228             sprintf(move, "%c%c%c%c%c\n",
4229                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4230         }
4231     }
4232 }
4233
4234 void
4235 ProcessICSInitScript(f)
4236      FILE *f;
4237 {
4238     char buf[MSG_SIZ];
4239
4240     while (fgets(buf, MSG_SIZ, f)) {
4241         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4242     }
4243
4244     fclose(f);
4245 }
4246
4247
4248 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4249 void
4250 AlphaRank(char *move, int n)
4251 {
4252 //    char *p = move, c; int x, y;
4253
4254     if (appData.debugMode) {
4255         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4256     }
4257
4258     if(move[1]=='*' &&
4259        move[2]>='0' && move[2]<='9' &&
4260        move[3]>='a' && move[3]<='x'    ) {
4261         move[1] = '@';
4262         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4263         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4264     } else
4265     if(move[0]>='0' && move[0]<='9' &&
4266        move[1]>='a' && move[1]<='x' &&
4267        move[2]>='0' && move[2]<='9' &&
4268        move[3]>='a' && move[3]<='x'    ) {
4269         /* input move, Shogi -> normal */
4270         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4271         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4272         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4273         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4274     } else
4275     if(move[1]=='@' &&
4276        move[3]>='0' && move[3]<='9' &&
4277        move[2]>='a' && move[2]<='x'    ) {
4278         move[1] = '*';
4279         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4280         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4281     } else
4282     if(
4283        move[0]>='a' && move[0]<='x' &&
4284        move[3]>='0' && move[3]<='9' &&
4285        move[2]>='a' && move[2]<='x'    ) {
4286          /* output move, normal -> Shogi */
4287         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4288         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4289         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4290         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4291         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4292     }
4293     if (appData.debugMode) {
4294         fprintf(debugFP, "   out = '%s'\n", move);
4295     }
4296 }
4297
4298 /* Parser for moves from gnuchess, ICS, or user typein box */
4299 Boolean
4300 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4301      char *move;
4302      int moveNum;
4303      ChessMove *moveType;
4304      int *fromX, *fromY, *toX, *toY;
4305      char *promoChar;
4306 {
4307     if (appData.debugMode) {
4308         fprintf(debugFP, "move to parse: %s\n", move);
4309     }
4310     *moveType = yylexstr(moveNum, move);
4311
4312     switch (*moveType) {
4313       case WhitePromotionChancellor:
4314       case BlackPromotionChancellor:
4315       case WhitePromotionArchbishop:
4316       case BlackPromotionArchbishop:
4317       case WhitePromotionQueen:
4318       case BlackPromotionQueen:
4319       case WhitePromotionRook:
4320       case BlackPromotionRook:
4321       case WhitePromotionBishop:
4322       case BlackPromotionBishop:
4323       case WhitePromotionKnight:
4324       case BlackPromotionKnight:
4325       case WhitePromotionKing:
4326       case BlackPromotionKing:
4327       case NormalMove:
4328       case WhiteCapturesEnPassant:
4329       case BlackCapturesEnPassant:
4330       case WhiteKingSideCastle:
4331       case WhiteQueenSideCastle:
4332       case BlackKingSideCastle:
4333       case BlackQueenSideCastle:
4334       case WhiteKingSideCastleWild:
4335       case WhiteQueenSideCastleWild:
4336       case BlackKingSideCastleWild:
4337       case BlackQueenSideCastleWild:
4338       /* Code added by Tord: */
4339       case WhiteHSideCastleFR:
4340       case WhiteASideCastleFR:
4341       case BlackHSideCastleFR:
4342       case BlackASideCastleFR:
4343       /* End of code added by Tord */
4344       case IllegalMove:         /* bug or odd chess variant */
4345         *fromX = currentMoveString[0] - AAA;
4346         *fromY = currentMoveString[1] - ONE;
4347         *toX = currentMoveString[2] - AAA;
4348         *toY = currentMoveString[3] - ONE;
4349         *promoChar = currentMoveString[4];
4350         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4351             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4352     if (appData.debugMode) {
4353         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4354     }
4355             *fromX = *fromY = *toX = *toY = 0;
4356             return FALSE;
4357         }
4358         if (appData.testLegality) {
4359           return (*moveType != IllegalMove);
4360         } else {
4361           return !(fromX == fromY && toX == toY);
4362         }
4363
4364       case WhiteDrop:
4365       case BlackDrop:
4366         *fromX = *moveType == WhiteDrop ?
4367           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4368           (int) CharToPiece(ToLower(currentMoveString[0]));
4369         *fromY = DROP_RANK;
4370         *toX = currentMoveString[2] - AAA;
4371         *toY = currentMoveString[3] - ONE;
4372         *promoChar = NULLCHAR;
4373         return TRUE;
4374
4375       case AmbiguousMove:
4376       case ImpossibleMove:
4377       case (ChessMove) 0:       /* end of file */
4378       case ElapsedTime:
4379       case Comment:
4380       case PGNTag:
4381       case NAG:
4382       case WhiteWins:
4383       case BlackWins:
4384       case GameIsDrawn:
4385       default:
4386     if (appData.debugMode) {
4387         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4388     }
4389         /* bug? */
4390         *fromX = *fromY = *toX = *toY = 0;
4391         *promoChar = NULLCHAR;
4392         return FALSE;
4393     }
4394 }
4395
4396 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4397 // All positions will have equal probability, but the current method will not provide a unique
4398 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4399 #define DARK 1
4400 #define LITE 2
4401 #define ANY 3
4402
4403 int squaresLeft[4];
4404 int piecesLeft[(int)BlackPawn];
4405 int seed, nrOfShuffles;
4406
4407 void GetPositionNumber()
4408 {       // sets global variable seed
4409         int i;
4410
4411         seed = appData.defaultFrcPosition;
4412         if(seed < 0) { // randomize based on time for negative FRC position numbers
4413                 for(i=0; i<50; i++) seed += random();
4414                 seed = random() ^ random() >> 8 ^ random() << 8;
4415                 if(seed<0) seed = -seed;
4416         }
4417 }
4418
4419 int put(Board board, int pieceType, int rank, int n, int shade)
4420 // put the piece on the (n-1)-th empty squares of the given shade
4421 {
4422         int i;
4423
4424         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4425                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4426                         board[rank][i] = (ChessSquare) pieceType;
4427                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4428                         squaresLeft[ANY]--;
4429                         piecesLeft[pieceType]--;
4430                         return i;
4431                 }
4432         }
4433         return -1;
4434 }
4435
4436
4437 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4438 // calculate where the next piece goes, (any empty square), and put it there
4439 {
4440         int i;
4441
4442         i = seed % squaresLeft[shade];
4443         nrOfShuffles *= squaresLeft[shade];
4444         seed /= squaresLeft[shade];
4445         put(board, pieceType, rank, i, shade);
4446 }
4447
4448 void AddTwoPieces(Board board, int pieceType, int rank)
4449 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4450 {
4451         int i, n=squaresLeft[ANY], j=n-1, k;
4452
4453         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4454         i = seed % k;  // pick one
4455         nrOfShuffles *= k;
4456         seed /= k;
4457         while(i >= j) i -= j--;
4458         j = n - 1 - j; i += j;
4459         put(board, pieceType, rank, j, ANY);
4460         put(board, pieceType, rank, i, ANY);
4461 }
4462
4463 void SetUpShuffle(Board board, int number)
4464 {
4465         int i, p, first=1;
4466
4467         GetPositionNumber(); nrOfShuffles = 1;
4468
4469         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4470         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4471         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4472
4473         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4474
4475         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4476             p = (int) board[0][i];
4477             if(p < (int) BlackPawn) piecesLeft[p] ++;
4478             board[0][i] = EmptySquare;
4479         }
4480
4481         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4482             // shuffles restricted to allow normal castling put KRR first
4483             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4484                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4485             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4486                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4487             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4488                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4489             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4490                 put(board, WhiteRook, 0, 0, ANY);
4491             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4492         }
4493
4494         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4495             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4496             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4497                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4498                 while(piecesLeft[p] >= 2) {
4499                     AddOnePiece(board, p, 0, LITE);
4500                     AddOnePiece(board, p, 0, DARK);
4501                 }
4502                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4503             }
4504
4505         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4506             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4507             // but we leave King and Rooks for last, to possibly obey FRC restriction
4508             if(p == (int)WhiteRook) continue;
4509             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4510             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4511         }
4512
4513         // now everything is placed, except perhaps King (Unicorn) and Rooks
4514
4515         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4516             // Last King gets castling rights
4517             while(piecesLeft[(int)WhiteUnicorn]) {
4518                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4519                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4520             }
4521
4522             while(piecesLeft[(int)WhiteKing]) {
4523                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4524                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4525             }
4526
4527
4528         } else {
4529             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4530             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4531         }
4532
4533         // Only Rooks can be left; simply place them all
4534         while(piecesLeft[(int)WhiteRook]) {
4535                 i = put(board, WhiteRook, 0, 0, ANY);
4536                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4537                         if(first) {
4538                                 first=0;
4539                                 initialRights[1]  = initialRights[4]  = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4540                         }
4541                         initialRights[0]  = initialRights[3]  = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4542                 }
4543         }
4544         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4545             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4546         }
4547
4548         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4549 }
4550
4551 int SetCharTable( char *table, const char * map )
4552 /* [HGM] moved here from winboard.c because of its general usefulness */
4553 /*       Basically a safe strcpy that uses the last character as King */
4554 {
4555     int result = FALSE; int NrPieces;
4556
4557     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4558                     && NrPieces >= 12 && !(NrPieces&1)) {
4559         int i; /* [HGM] Accept even length from 12 to 34 */
4560
4561         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4562         for( i=0; i<NrPieces/2-1; i++ ) {
4563             table[i] = map[i];
4564             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4565         }
4566         table[(int) WhiteKing]  = map[NrPieces/2-1];
4567         table[(int) BlackKing]  = map[NrPieces-1];
4568
4569         result = TRUE;
4570     }
4571
4572     return result;
4573 }
4574
4575 void Prelude(Board board)
4576 {       // [HGM] superchess: random selection of exo-pieces
4577         int i, j, k; ChessSquare p;
4578         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4579
4580         GetPositionNumber(); // use FRC position number
4581
4582         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4583             SetCharTable(pieceToChar, appData.pieceToCharTable);
4584             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4585                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4586         }
4587
4588         j = seed%4;                 seed /= 4;
4589         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4592         j = seed%3 + (seed%3 >= j); seed /= 3;
4593         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4594         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4595         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4596         j = seed%3;                 seed /= 3;
4597         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4598         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4599         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4600         j = seed%2 + (seed%2 >= j); seed /= 2;
4601         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4602         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4603         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4604         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4605         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4606         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4607         put(board, exoPieces[0],    0, 0, ANY);
4608         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4609 }
4610
4611 void
4612 InitPosition(redraw)
4613      int redraw;
4614 {
4615     ChessSquare (* pieces)[BOARD_FILES];
4616     int i, j, pawnRow, overrule,
4617     oldx = gameInfo.boardWidth,
4618     oldy = gameInfo.boardHeight,
4619     oldh = gameInfo.holdingsWidth,
4620     oldv = gameInfo.variant;
4621
4622     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4623
4624     /* [AS] Initialize pv info list [HGM] and game status */
4625     {
4626         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4627             pvInfoList[i].depth = 0;
4628             boards[i][EP_STATUS] = EP_NONE;
4629             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4630         }
4631
4632         initialRulePlies = 0; /* 50-move counter start */
4633
4634         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4635         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4636     }
4637
4638
4639     /* [HGM] logic here is completely changed. In stead of full positions */
4640     /* the initialized data only consist of the two backranks. The switch */
4641     /* selects which one we will use, which is than copied to the Board   */
4642     /* initialPosition, which for the rest is initialized by Pawns and    */
4643     /* empty squares. This initial position is then copied to boards[0],  */
4644     /* possibly after shuffling, so that it remains available.            */
4645
4646     gameInfo.holdingsWidth = 0; /* default board sizes */
4647     gameInfo.boardWidth    = 8;
4648     gameInfo.boardHeight   = 8;
4649     gameInfo.holdingsSize  = 0;
4650     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4651     for(i=0; i<BOARD_FILES-2; i++)
4652       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4653     initialPosition[EP_STATUS] = EP_NONE;
4654     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4655
4656     switch (gameInfo.variant) {
4657     case VariantFischeRandom:
4658       shuffleOpenings = TRUE;
4659     default:
4660       pieces = FIDEArray;
4661       break;
4662     case VariantShatranj:
4663       pieces = ShatranjArray;
4664       nrCastlingRights = 0;
4665       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4666       break;
4667     case VariantTwoKings:
4668       pieces = twoKingsArray;
4669       break;
4670     case VariantCapaRandom:
4671       shuffleOpenings = TRUE;
4672     case VariantCapablanca:
4673       pieces = CapablancaArray;
4674       gameInfo.boardWidth = 10;
4675       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4676       break;
4677     case VariantGothic:
4678       pieces = GothicArray;
4679       gameInfo.boardWidth = 10;
4680       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4681       break;
4682     case VariantJanus:
4683       pieces = JanusArray;
4684       gameInfo.boardWidth = 10;
4685       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4686       nrCastlingRights = 6;
4687         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4688         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4689         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4690         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4691         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4692         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4693       break;
4694     case VariantFalcon:
4695       pieces = FalconArray;
4696       gameInfo.boardWidth = 10;
4697       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4698       break;
4699     case VariantXiangqi:
4700       pieces = XiangqiArray;
4701       gameInfo.boardWidth  = 9;
4702       gameInfo.boardHeight = 10;
4703       nrCastlingRights = 0;
4704       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4705       break;
4706     case VariantShogi:
4707       pieces = ShogiArray;
4708       gameInfo.boardWidth  = 9;
4709       gameInfo.boardHeight = 9;
4710       gameInfo.holdingsSize = 7;
4711       nrCastlingRights = 0;
4712       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4713       break;
4714     case VariantCourier:
4715       pieces = CourierArray;
4716       gameInfo.boardWidth  = 12;
4717       nrCastlingRights = 0;
4718       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4719       break;
4720     case VariantKnightmate:
4721       pieces = KnightmateArray;
4722       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4723       break;
4724     case VariantFairy:
4725       pieces = fairyArray;
4726       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4727       break;
4728     case VariantGreat:
4729       pieces = GreatArray;
4730       gameInfo.boardWidth = 10;
4731       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4732       gameInfo.holdingsSize = 8;
4733       break;
4734     case VariantSuper:
4735       pieces = FIDEArray;
4736       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4737       gameInfo.holdingsSize = 8;
4738       startedFromSetupPosition = TRUE;
4739       break;
4740     case VariantCrazyhouse:
4741     case VariantBughouse:
4742       pieces = FIDEArray;
4743       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4744       gameInfo.holdingsSize = 5;
4745       break;
4746     case VariantWildCastle:
4747       pieces = FIDEArray;
4748       /* !!?shuffle with kings guaranteed to be on d or e file */
4749       shuffleOpenings = 1;
4750       break;
4751     case VariantNoCastle:
4752       pieces = FIDEArray;
4753       nrCastlingRights = 0;
4754       /* !!?unconstrained back-rank shuffle */
4755       shuffleOpenings = 1;
4756       break;
4757     }
4758
4759     overrule = 0;
4760     if(appData.NrFiles >= 0) {
4761         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4762         gameInfo.boardWidth = appData.NrFiles;
4763     }
4764     if(appData.NrRanks >= 0) {
4765         gameInfo.boardHeight = appData.NrRanks;
4766     }
4767     if(appData.holdingsSize >= 0) {
4768         i = appData.holdingsSize;
4769         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4770         gameInfo.holdingsSize = i;
4771     }
4772     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4773     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4774         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4775
4776     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4777     if(pawnRow < 1) pawnRow = 1;
4778
4779     /* User pieceToChar list overrules defaults */
4780     if(appData.pieceToCharTable != NULL)
4781         SetCharTable(pieceToChar, appData.pieceToCharTable);
4782
4783     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4784
4785         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4786             s = (ChessSquare) 0; /* account holding counts in guard band */
4787         for( i=0; i<BOARD_HEIGHT; i++ )
4788             initialPosition[i][j] = s;
4789
4790         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4791         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4792         initialPosition[pawnRow][j] = WhitePawn;
4793         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4794         if(gameInfo.variant == VariantXiangqi) {
4795             if(j&1) {
4796                 initialPosition[pawnRow][j] =
4797                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4798                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4799                    initialPosition[2][j] = WhiteCannon;
4800                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4801                 }
4802             }
4803         }
4804         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4805     }
4806     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4807
4808             j=BOARD_LEFT+1;
4809             initialPosition[1][j] = WhiteBishop;
4810             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4811             j=BOARD_RGHT-2;
4812             initialPosition[1][j] = WhiteRook;
4813             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4814     }
4815
4816     if( nrCastlingRights == -1) {
4817         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4818         /*       This sets default castling rights from none to normal corners   */
4819         /* Variants with other castling rights must set them themselves above    */
4820         nrCastlingRights = 6;
4821         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4822         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4823         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4824         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4825         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4826         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4827      }
4828
4829      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4830      if(gameInfo.variant == VariantGreat) { // promotion commoners
4831         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4832         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4833         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4834         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4835      }
4836   if (appData.debugMode) {
4837     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4838   }
4839     if(shuffleOpenings) {
4840         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4841         startedFromSetupPosition = TRUE;
4842     }
4843     if(startedFromPositionFile) {
4844       /* [HGM] loadPos: use PositionFile for every new game */
4845       CopyBoard(initialPosition, filePosition);
4846       for(i=0; i<nrCastlingRights; i++)
4847           initialRights[i] = filePosition[CASTLING][i];
4848       startedFromSetupPosition = TRUE;
4849     }
4850
4851     CopyBoard(boards[0], initialPosition);
4852     if(oldx != gameInfo.boardWidth ||
4853        oldy != gameInfo.boardHeight ||
4854        oldh != gameInfo.holdingsWidth
4855 #ifdef GOTHIC
4856        || oldv == VariantGothic ||        // For licensing popups
4857        gameInfo.variant == VariantGothic
4858 #endif
4859 #ifdef FALCON
4860        || oldv == VariantFalcon ||
4861        gameInfo.variant == VariantFalcon
4862 #endif
4863                                          )
4864       {
4865             InitDrawingSizes(-2 ,0);
4866       }
4867
4868     if (redraw)
4869       DrawPosition(TRUE, boards[currentMove]);
4870
4871 }
4872
4873 void
4874 SendBoard(cps, moveNum)
4875      ChessProgramState *cps;
4876      int moveNum;
4877 {
4878     char message[MSG_SIZ];
4879
4880     if (cps->useSetboard) {
4881       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4882       sprintf(message, "setboard %s\n", fen);
4883       SendToProgram(message, cps);
4884       free(fen);
4885
4886     } else {
4887       ChessSquare *bp;
4888       int i, j;
4889       /* Kludge to set black to move, avoiding the troublesome and now
4890        * deprecated "black" command.
4891        */
4892       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4893
4894       SendToProgram("edit\n", cps);
4895       SendToProgram("#\n", cps);
4896       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4897         bp = &boards[moveNum][i][BOARD_LEFT];
4898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4899           if ((int) *bp < (int) BlackPawn) {
4900             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4901                     AAA + j, ONE + i);
4902             if(message[0] == '+' || message[0] == '~') {
4903                 sprintf(message, "%c%c%c+\n",
4904                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4905                         AAA + j, ONE + i);
4906             }
4907             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4908                 message[1] = BOARD_RGHT   - 1 - j + '1';
4909                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4910             }
4911             SendToProgram(message, cps);
4912           }
4913         }
4914       }
4915
4916       SendToProgram("c\n", cps);
4917       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4918         bp = &boards[moveNum][i][BOARD_LEFT];
4919         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4920           if (((int) *bp != (int) EmptySquare)
4921               && ((int) *bp >= (int) BlackPawn)) {
4922             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4923                     AAA + j, ONE + i);
4924             if(message[0] == '+' || message[0] == '~') {
4925                 sprintf(message, "%c%c%c+\n",
4926                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4927                         AAA + j, ONE + i);
4928             }
4929             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4930                 message[1] = BOARD_RGHT   - 1 - j + '1';
4931                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4932             }
4933             SendToProgram(message, cps);
4934           }
4935         }
4936       }
4937
4938       SendToProgram(".\n", cps);
4939     }
4940     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4941 }
4942
4943 int
4944 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4945 {
4946     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4947     /* [HGM] add Shogi promotions */
4948     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4949     ChessSquare piece;
4950     ChessMove moveType;
4951     Boolean premove;
4952
4953     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4954     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4955
4956     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4957       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4958         return FALSE;
4959
4960     piece = boards[currentMove][fromY][fromX];
4961     if(gameInfo.variant == VariantShogi) {
4962         promotionZoneSize = 3;
4963         highestPromotingPiece = (int)WhiteFerz;
4964     }
4965
4966     // next weed out all moves that do not touch the promotion zone at all
4967     if((int)piece >= BlackPawn) {
4968         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4969              return FALSE;
4970         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4971     } else {
4972         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4973            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4974     }
4975
4976     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4977
4978     // weed out mandatory Shogi promotions
4979     if(gameInfo.variant == VariantShogi) {
4980         if(piece >= BlackPawn) {
4981             if(toY == 0 && piece == BlackPawn ||
4982                toY == 0 && piece == BlackQueen ||
4983                toY <= 1 && piece == BlackKnight) {
4984                 *promoChoice = '+';
4985                 return FALSE;
4986             }
4987         } else {
4988             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4989                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4990                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4991                 *promoChoice = '+';
4992                 return FALSE;
4993             }
4994         }
4995     }
4996
4997     // weed out obviously illegal Pawn moves
4998     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4999         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5000         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5001         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5002         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5003         // note we are not allowed to test for valid (non-)capture, due to premove
5004     }
5005
5006     // we either have a choice what to promote to, or (in Shogi) whether to promote
5007     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5008         *promoChoice = PieceToChar(BlackFerz);  // no choice
5009         return FALSE;
5010     }
5011     if(appData.alwaysPromoteToQueen) { // predetermined
5012         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5013              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5014         else *promoChoice = PieceToChar(BlackQueen);
5015         return FALSE;
5016     }
5017
5018     // suppress promotion popup on illegal moves that are not premoves
5019     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5020               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5021     if(appData.testLegality && !premove) {
5022         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5023                         fromY, fromX, toY, toX, NULLCHAR);
5024         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5025            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5026             return FALSE;
5027     }
5028
5029     return TRUE;
5030 }
5031
5032 int
5033 InPalace(row, column)
5034      int row, column;
5035 {   /* [HGM] for Xiangqi */
5036     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5037          column < (BOARD_WIDTH + 4)/2 &&
5038          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5039     return FALSE;
5040 }
5041
5042 int
5043 PieceForSquare (x, y)
5044      int x;
5045      int y;
5046 {
5047   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5048      return -1;
5049   else
5050      return boards[currentMove][y][x];
5051 }
5052
5053 int
5054 OKToStartUserMove(x, y)
5055      int x, y;
5056 {
5057     ChessSquare from_piece;
5058     int white_piece;
5059
5060     if (matchMode) return FALSE;
5061     if (gameMode == EditPosition) return TRUE;
5062
5063     if (x >= 0 && y >= 0)
5064       from_piece = boards[currentMove][y][x];
5065     else
5066       from_piece = EmptySquare;
5067
5068     if (from_piece == EmptySquare) return FALSE;
5069
5070     white_piece = (int)from_piece >= (int)WhitePawn &&
5071       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5072
5073     switch (gameMode) {
5074       case PlayFromGameFile:
5075       case AnalyzeFile:
5076       case TwoMachinesPlay:
5077       case EndOfGame:
5078         return FALSE;
5079
5080       case IcsObserving:
5081       case IcsIdle:
5082         return FALSE;
5083
5084       case MachinePlaysWhite:
5085       case IcsPlayingBlack:
5086         if (appData.zippyPlay) return FALSE;
5087         if (white_piece) {
5088             DisplayMoveError(_("You are playing Black"));
5089             return FALSE;
5090         }
5091         break;
5092
5093       case MachinePlaysBlack:
5094       case IcsPlayingWhite:
5095         if (appData.zippyPlay) return FALSE;
5096         if (!white_piece) {
5097             DisplayMoveError(_("You are playing White"));
5098             return FALSE;
5099         }
5100         break;
5101
5102       case EditGame:
5103         if (!white_piece && WhiteOnMove(currentMove)) {
5104             DisplayMoveError(_("It is White's turn"));
5105             return FALSE;
5106         }
5107         if (white_piece && !WhiteOnMove(currentMove)) {
5108             DisplayMoveError(_("It is Black's turn"));
5109             return FALSE;
5110         }
5111         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5112             /* Editing correspondence game history */
5113             /* Could disallow this or prompt for confirmation */
5114             cmailOldMove = -1;
5115         }
5116         break;
5117
5118       case BeginningOfGame:
5119         if (appData.icsActive) return FALSE;
5120         if (!appData.noChessProgram) {
5121             if (!white_piece) {
5122                 DisplayMoveError(_("You are playing White"));
5123                 return FALSE;
5124             }
5125         }
5126         break;
5127
5128       case Training:
5129         if (!white_piece && WhiteOnMove(currentMove)) {
5130             DisplayMoveError(_("It is White's turn"));
5131             return FALSE;
5132         }
5133         if (white_piece && !WhiteOnMove(currentMove)) {
5134             DisplayMoveError(_("It is Black's turn"));
5135             return FALSE;
5136         }
5137         break;
5138
5139       default:
5140       case IcsExamining:
5141         break;
5142     }
5143     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5144         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5145         && gameMode != AnalyzeFile && gameMode != Training) {
5146         DisplayMoveError(_("Displayed position is not current"));
5147         return FALSE;
5148     }
5149     return TRUE;
5150 }
5151
5152 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5153 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5154 int lastLoadGameUseList = FALSE;
5155 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5156 ChessMove lastLoadGameStart = (ChessMove) 0;
5157
5158 ChessMove
5159 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5160      int fromX, fromY, toX, toY;
5161      int promoChar;
5162      Boolean captureOwn;
5163 {
5164     ChessMove moveType;
5165     ChessSquare pdown, pup;
5166
5167     /* Check if the user is playing in turn.  This is complicated because we
5168        let the user "pick up" a piece before it is his turn.  So the piece he
5169        tried to pick up may have been captured by the time he puts it down!
5170        Therefore we use the color the user is supposed to be playing in this
5171        test, not the color of the piece that is currently on the starting
5172        square---except in EditGame mode, where the user is playing both
5173        sides; fortunately there the capture race can't happen.  (It can
5174        now happen in IcsExamining mode, but that's just too bad.  The user
5175        will get a somewhat confusing message in that case.)
5176        */
5177
5178     switch (gameMode) {
5179       case PlayFromGameFile:
5180       case AnalyzeFile:
5181       case TwoMachinesPlay:
5182       case EndOfGame:
5183       case IcsObserving:
5184       case IcsIdle:
5185         /* We switched into a game mode where moves are not accepted,
5186            perhaps while the mouse button was down. */
5187         return ImpossibleMove;
5188
5189       case MachinePlaysWhite:
5190         /* User is moving for Black */
5191         if (WhiteOnMove(currentMove)) {
5192             DisplayMoveError(_("It is White's turn"));
5193             return ImpossibleMove;
5194         }
5195         break;
5196
5197       case MachinePlaysBlack:
5198         /* User is moving for White */
5199         if (!WhiteOnMove(currentMove)) {
5200             DisplayMoveError(_("It is Black's turn"));
5201             return ImpossibleMove;
5202         }
5203         break;
5204
5205       case EditGame:
5206       case IcsExamining:
5207       case BeginningOfGame:
5208       case AnalyzeMode:
5209       case Training:
5210         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5211             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5212             /* User is moving for Black */
5213             if (WhiteOnMove(currentMove)) {
5214                 DisplayMoveError(_("It is White's turn"));
5215                 return ImpossibleMove;
5216             }
5217         } else {
5218             /* User is moving for White */
5219             if (!WhiteOnMove(currentMove)) {
5220                 DisplayMoveError(_("It is Black's turn"));
5221                 return ImpossibleMove;
5222             }
5223         }
5224         break;
5225
5226       case IcsPlayingBlack:
5227         /* User is moving for Black */
5228         if (WhiteOnMove(currentMove)) {
5229             if (!appData.premove) {
5230                 DisplayMoveError(_("It is White's turn"));
5231             } else if (toX >= 0 && toY >= 0) {
5232                 premoveToX = toX;
5233                 premoveToY = toY;
5234                 premoveFromX = fromX;
5235                 premoveFromY = fromY;
5236                 premovePromoChar = promoChar;
5237                 gotPremove = 1;
5238                 if (appData.debugMode)
5239                     fprintf(debugFP, "Got premove: fromX %d,"
5240                             "fromY %d, toX %d, toY %d\n",
5241                             fromX, fromY, toX, toY);
5242             }
5243             return ImpossibleMove;
5244         }
5245         break;
5246
5247       case IcsPlayingWhite:
5248         /* User is moving for White */
5249         if (!WhiteOnMove(currentMove)) {
5250             if (!appData.premove) {
5251                 DisplayMoveError(_("It is Black's turn"));
5252             } else if (toX >= 0 && toY >= 0) {
5253                 premoveToX = toX;
5254                 premoveToY = toY;
5255                 premoveFromX = fromX;
5256                 premoveFromY = fromY;
5257                 premovePromoChar = promoChar;
5258                 gotPremove = 1;
5259                 if (appData.debugMode)
5260                     fprintf(debugFP, "Got premove: fromX %d,"
5261                             "fromY %d, toX %d, toY %d\n",
5262                             fromX, fromY, toX, toY);
5263             }
5264             return ImpossibleMove;
5265         }
5266         break;
5267
5268       default:
5269         break;
5270
5271       case EditPosition:
5272         /* EditPosition, empty square, or different color piece;
5273            click-click move is possible */
5274         if (toX == -2 || toY == -2) {
5275             boards[0][fromY][fromX] = EmptySquare;
5276             return AmbiguousMove;
5277         } else if (toX >= 0 && toY >= 0) {
5278             boards[0][toY][toX] = boards[0][fromY][fromX];
5279             boards[0][fromY][fromX] = EmptySquare;
5280             return AmbiguousMove;
5281         }
5282         return ImpossibleMove;
5283     }
5284
5285     if(toX < 0 || toY < 0) return ImpossibleMove;
5286     pdown = boards[currentMove][fromY][fromX];
5287     pup = boards[currentMove][toY][toX];
5288
5289     /* [HGM] If move started in holdings, it means a drop */
5290     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5291          if( pup != EmptySquare ) return ImpossibleMove;
5292          if(appData.testLegality) {
5293              /* it would be more logical if LegalityTest() also figured out
5294               * which drops are legal. For now we forbid pawns on back rank.
5295               * Shogi is on its own here...
5296               */
5297              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5298                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5299                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5300          }
5301          return WhiteDrop; /* Not needed to specify white or black yet */
5302     }
5303
5304     userOfferedDraw = FALSE;
5305
5306     /* [HGM] always test for legality, to get promotion info */
5307     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5308                                          fromY, fromX, toY, toX, promoChar);
5309     /* [HGM] but possibly ignore an IllegalMove result */
5310     if (appData.testLegality) {
5311         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5312             DisplayMoveError(_("Illegal move"));
5313             return ImpossibleMove;
5314         }
5315     }
5316
5317     return moveType;
5318     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5319        function is made into one that returns an OK move type if FinishMove
5320        should be called. This to give the calling driver routine the
5321        opportunity to finish the userMove input with a promotion popup,
5322        without bothering the user with this for invalid or illegal moves */
5323
5324 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5325 }
5326
5327 /* Common tail of UserMoveEvent and DropMenuEvent */
5328 int
5329 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5330      ChessMove moveType;
5331      int fromX, fromY, toX, toY;
5332      /*char*/int promoChar;
5333 {
5334   char *bookHit = 0;
5335
5336   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5337     {
5338       // [HGM] superchess: suppress promotions to non-available piece
5339       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340       if(WhiteOnMove(currentMove))
5341         {
5342           if(!boards[currentMove][k][BOARD_WIDTH-2])
5343             return 0;
5344         }
5345       else
5346         {
5347           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5348             return 0;
5349         }
5350     }
5351   
5352   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5353      move type in caller when we know the move is a legal promotion */
5354   if(moveType == NormalMove && promoChar)
5355     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5356   
5357   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5358      move type in caller when we know the move is a legal promotion */
5359   if(moveType == NormalMove && promoChar)
5360     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5361   
5362   /* [HGM] convert drag-and-drop piece drops to standard form */
5363   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5364     {
5365       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5366       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5367                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5368       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5369       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5370       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5371       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5372       fromY = DROP_RANK;
5373     }
5374   
5375   /* [HGM] <popupFix> The following if has been moved here from
5376      UserMoveEvent(). Because it seemed to belong here (why not allow
5377      piece drops in training games?), and because it can only be
5378      performed after it is known to what we promote. */
5379   if (gameMode == Training) 
5380     {
5381       /* compare the move played on the board to the next move in the
5382        * game. If they match, display the move and the opponent's response.
5383        * If they don't match, display an error message.
5384        */
5385       int saveAnimate;
5386       Board testBoard;
5387       CopyBoard(testBoard, boards[currentMove]);
5388       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5389
5390       if (CompareBoards(testBoard, boards[currentMove+1]))
5391         {
5392           ForwardInner(currentMove+1);
5393
5394           /* Autoplay the opponent's response.
5395            * if appData.animate was TRUE when Training mode was entered,
5396            * the response will be animated.
5397            */
5398           saveAnimate = appData.animate;
5399           appData.animate = animateTraining;
5400           ForwardInner(currentMove+1);
5401           appData.animate = saveAnimate;
5402
5403           /* check for the end of the game */
5404           if (currentMove >= forwardMostMove)
5405             {
5406               gameMode = PlayFromGameFile;
5407               ModeHighlight();
5408               SetTrainingModeOff();
5409               DisplayInformation(_("End of game"));
5410             }
5411         }
5412       else
5413         {
5414           DisplayError(_("Incorrect move"), 0);
5415         }
5416       return 1;
5417     }
5418
5419   /* Ok, now we know that the move is good, so we can kill
5420      the previous line in Analysis Mode */
5421   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5422                                 && currentMove < forwardMostMove) {
5423     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5424   }
5425
5426   /* If we need the chess program but it's dead, restart it */
5427   ResurrectChessProgram();
5428
5429   /* A user move restarts a paused game*/
5430   if (pausing)
5431     PauseEvent();
5432
5433   thinkOutput[0] = NULLCHAR;
5434
5435   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5436
5437   if (gameMode == BeginningOfGame)
5438     {
5439       if (appData.noChessProgram)
5440         {
5441           gameMode = EditGame;
5442           SetGameInfo();
5443         }
5444       else
5445         {
5446           char buf[MSG_SIZ];
5447           gameMode = MachinePlaysBlack;
5448           StartClocks();
5449           SetGameInfo();
5450           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5451           DisplayTitle(buf);
5452           if (first.sendName)
5453             {
5454               sprintf(buf, "name %s\n", gameInfo.white);
5455               SendToProgram(buf, &first);
5456             }
5457           StartClocks();
5458         }
5459       ModeHighlight();
5460     }
5461
5462   /* Relay move to ICS or chess engine */
5463   if (appData.icsActive)
5464     {
5465       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5466           gameMode == IcsExamining)
5467         {
5468           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5469           ics_user_moved = 1;
5470         }
5471     }
5472   else
5473     {
5474       if (first.sendTime && (gameMode == BeginningOfGame ||
5475                              gameMode == MachinePlaysWhite ||
5476                              gameMode == MachinePlaysBlack))
5477         {
5478           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5479         }
5480       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5481         {
5482           // [HGM] book: if program might be playing, let it use book
5483           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5484           first.maybeThinking = TRUE;
5485         }
5486       else
5487         SendMoveToProgram(forwardMostMove-1, &first);
5488       if (currentMove == cmailOldMove + 1)
5489         {
5490           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5491         }
5492     }
5493
5494   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5495
5496   switch (gameMode) 
5497     {
5498     case EditGame:
5499       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
5500         {
5501         case MT_NONE:
5502         case MT_CHECK:
5503           break;
5504         case MT_CHECKMATE:
5505         case MT_STAINMATE:
5506           if (WhiteOnMove(currentMove)) {
5507             GameEnds(BlackWins, "Black mates", GE_PLAYER);
5508           } else {
5509             GameEnds(WhiteWins, "White mates", GE_PLAYER);
5510           }
5511           break;
5512         case MT_STALEMATE:
5513           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5514           break;
5515         }
5516       break;
5517       
5518     case MachinePlaysBlack:
5519     case MachinePlaysWhite:
5520       /* disable certain menu options while machine is thinking */
5521       SetMachineThinkingEnables();
5522       break;
5523       
5524     default:
5525       break;
5526     }
5527   
5528   if(bookHit)
5529     { // [HGM] book: simulate book reply
5530       static char bookMove[MSG_SIZ]; // a bit generous?
5531
5532       programStats.nodes = programStats.depth = programStats.time =
5533         programStats.score = programStats.got_only_move = 0;
5534       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5535
5536       strcpy(bookMove, "move ");
5537       strcat(bookMove, bookHit);
5538       HandleMachineMove(bookMove, &first);
5539     }
5540
5541   return 1;
5542 }
5543
5544 void
5545 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5546      int fromX, fromY, toX, toY;
5547      int promoChar;
5548 {
5549     /* [HGM] This routine was added to allow calling of its two logical
5550        parts from other modules in the old way. Before, UserMoveEvent()
5551        automatically called FinishMove() if the move was OK, and returned
5552        otherwise. I separated the two, in order to make it possible to
5553        slip a promotion popup in between. But that it always needs two
5554        calls, to the first part, (now called UserMoveTest() ), and to
5555        FinishMove if the first part succeeded. Calls that do not need
5556        to do anything in between, can call this routine the old way.
5557     */
5558   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5559   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5560   if(moveType == AmbiguousMove)
5561     DrawPosition(FALSE, boards[currentMove]);
5562   else if(moveType != ImpossibleMove && moveType != Comment)
5563     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5564 }
5565
5566 void LeftClick(ClickType clickType, int xPix, int yPix)
5567 {
5568     int x, y;
5569     Boolean saveAnimate;
5570     static int second = 0, promotionChoice = 0;
5571     char promoChoice = NULLCHAR;
5572
5573     if (clickType == Press) ErrorPopDown();
5574
5575     x = EventToSquare(xPix, BOARD_WIDTH);
5576     y = EventToSquare(yPix, BOARD_HEIGHT);
5577     if (!flipView && y >= 0) {
5578         y = BOARD_HEIGHT - 1 - y;
5579     }
5580     if (flipView && x >= 0) {
5581         x = BOARD_WIDTH - 1 - x;
5582     }
5583
5584     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5585         if(clickType == Release) return; // ignore upclick of click-click destination
5586         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5587         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5588         if(gameInfo.holdingsWidth && 
5589                 (WhiteOnMove(currentMove) 
5590                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5591                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5592             // click in right holdings, for determining promotion piece
5593             ChessSquare p = boards[currentMove][y][x];
5594             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5595             if(p != EmptySquare) {
5596                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5597                 fromX = fromY = -1;
5598                 return;
5599             }
5600         }
5601         DrawPosition(FALSE, boards[currentMove]);
5602         return;
5603     }
5604
5605     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5606     if(clickType == Press
5607             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5608               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5609               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5610         return;
5611
5612     if (fromX == -1) {
5613         if (clickType == Press) {
5614             /* First square */
5615             if (OKToStartUserMove(x, y)) {
5616                 fromX = x;
5617                 fromY = y;
5618                 second = 0;
5619                 DragPieceBegin(xPix, yPix);
5620                 if (appData.highlightDragging) {
5621                     SetHighlights(x, y, -1, -1);
5622                 }
5623             }
5624         }
5625         return;
5626     }
5627
5628     /* fromX != -1 */
5629     if (clickType == Press && gameMode != EditPosition) {
5630         ChessSquare fromP;
5631         ChessSquare toP;
5632         int frc;
5633
5634         // ignore off-board to clicks
5635         if(y < 0 || x < 0) return;
5636
5637         /* Check if clicking again on the same color piece */
5638         fromP = boards[currentMove][fromY][fromX];
5639         toP = boards[currentMove][y][x];
5640         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5641         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5642              WhitePawn <= toP && toP <= WhiteKing &&
5643              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5644              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5645             (BlackPawn <= fromP && fromP <= BlackKing && 
5646              BlackPawn <= toP && toP <= BlackKing &&
5647              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5648              !(fromP == BlackKing && toP == BlackRook && frc))) {
5649             /* Clicked again on same color piece -- changed his mind */
5650             second = (x == fromX && y == fromY);
5651             if (appData.highlightDragging) {
5652                 SetHighlights(x, y, -1, -1);
5653             } else {
5654                 ClearHighlights();
5655             }
5656             if (OKToStartUserMove(x, y)) {
5657                 fromX = x;
5658                 fromY = y;
5659                 DragPieceBegin(xPix, yPix);
5660             }
5661             return;
5662         }
5663         // ignore clicks on holdings
5664         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5665     }
5666
5667     if (clickType == Release && x == fromX && y == fromY) {
5668         DragPieceEnd(xPix, yPix);
5669         if (appData.animateDragging) {
5670             /* Undo animation damage if any */
5671             DrawPosition(FALSE, NULL);
5672         }
5673         if (second) {
5674             /* Second up/down in same square; just abort move */
5675             second = 0;
5676             fromX = fromY = -1;
5677             ClearHighlights();
5678             gotPremove = 0;
5679             ClearPremoveHighlights();
5680         } else {
5681             /* First upclick in same square; start click-click mode */
5682             SetHighlights(x, y, -1, -1);
5683         }
5684         return;
5685     }
5686
5687     /* we now have a different from- and (possibly off-board) to-square */
5688     /* Completed move */
5689     toX = x;
5690     toY = y;
5691     saveAnimate = appData.animate;
5692     if (clickType == Press) {
5693         /* Finish clickclick move */
5694         if (appData.animate || appData.highlightLastMove) {
5695             SetHighlights(fromX, fromY, toX, toY);
5696         } else {
5697             ClearHighlights();
5698         }
5699     } else {
5700         /* Finish drag move */
5701         if (appData.highlightLastMove) {
5702             SetHighlights(fromX, fromY, toX, toY);
5703         } else {
5704             ClearHighlights();
5705         }
5706         DragPieceEnd(xPix, yPix);
5707         /* Don't animate move and drag both */
5708         appData.animate = FALSE;
5709     }
5710
5711     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5712     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5713         ClearHighlights();
5714         fromX = fromY = -1;
5715         DrawPosition(TRUE, NULL);
5716         return;
5717     }
5718
5719     // off-board moves should not be highlighted
5720     if(x < 0 || x < 0) ClearHighlights();
5721
5722     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5723         SetHighlights(fromX, fromY, toX, toY);
5724         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5725             // [HGM] super: promotion to captured piece selected from holdings
5726             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5727             promotionChoice = TRUE;
5728             // kludge follows to temporarily execute move on display, without promoting yet
5729             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5730             boards[currentMove][toY][toX] = p;
5731             DrawPosition(FALSE, boards[currentMove]);
5732             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5733             boards[currentMove][toY][toX] = q;
5734             DisplayMessage("Click in holdings to choose piece", "");
5735             return;
5736         }
5737         PromotionPopUp();
5738     } else {
5739         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5740         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5741         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5742         fromX = fromY = -1;
5743     }
5744     appData.animate = saveAnimate;
5745     if (appData.animate || appData.animateDragging) {
5746         /* Undo animation damage if needed */
5747         DrawPosition(FALSE, NULL);
5748     }
5749 }
5750
5751 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5752 {
5753 //    char * hint = lastHint;
5754     FrontEndProgramStats stats;
5755
5756     stats.which = cps == &first ? 0 : 1;
5757     stats.depth = cpstats->depth;
5758     stats.nodes = cpstats->nodes;
5759     stats.score = cpstats->score;
5760     stats.time = cpstats->time;
5761     stats.pv = cpstats->movelist;
5762     stats.hint = lastHint;
5763     stats.an_move_index = 0;
5764     stats.an_move_count = 0;
5765
5766     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5767         stats.hint = cpstats->move_name;
5768         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5769         stats.an_move_count = cpstats->nr_moves;
5770     }
5771
5772     SetProgramStats( &stats );
5773 }
5774
5775 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5776 {   // [HGM] book: this routine intercepts moves to simulate book replies
5777     char *bookHit = NULL;
5778
5779     //first determine if the incoming move brings opponent into his book
5780     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5781         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5782     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5783     if(bookHit != NULL && !cps->bookSuspend) {
5784         // make sure opponent is not going to reply after receiving move to book position
5785         SendToProgram("force\n", cps);
5786         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5787     }
5788     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5789     // now arrange restart after book miss
5790     if(bookHit) {
5791         // after a book hit we never send 'go', and the code after the call to this routine
5792         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5793         char buf[MSG_SIZ];
5794         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5795         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5796         SendToProgram(buf, cps);
5797         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5798     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5799         SendToProgram("go\n", cps);
5800         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5801     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5802         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5803             SendToProgram("go\n", cps);
5804         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5805     }
5806     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5807 }
5808
5809 char *savedMessage;
5810 ChessProgramState *savedState;
5811 void DeferredBookMove(void)
5812 {
5813         if(savedState->lastPing != savedState->lastPong)
5814                     ScheduleDelayedEvent(DeferredBookMove, 10);
5815         else
5816         HandleMachineMove(savedMessage, savedState);
5817 }
5818
5819 void
5820 HandleMachineMove(message, cps)
5821      char *message;
5822      ChessProgramState *cps;
5823 {
5824     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5825     char realname[MSG_SIZ];
5826     int fromX, fromY, toX, toY;
5827     ChessMove moveType;
5828     char promoChar;
5829     char *p;
5830     int machineWhite;
5831     char *bookHit;
5832
5833 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5834     /*
5835      * Kludge to ignore BEL characters
5836      */
5837     while (*message == '\007') message++;
5838
5839     /*
5840      * [HGM] engine debug message: ignore lines starting with '#' character
5841      */
5842     if(cps->debug && *message == '#') return;
5843
5844     /*
5845      * Look for book output
5846      */
5847     if (cps == &first && bookRequested) {
5848         if (message[0] == '\t' || message[0] == ' ') {
5849             /* Part of the book output is here; append it */
5850             strcat(bookOutput, message);
5851             strcat(bookOutput, "  \n");
5852             return;
5853         } else if (bookOutput[0] != NULLCHAR) {
5854             /* All of book output has arrived; display it */
5855             char *p = bookOutput;
5856             while (*p != NULLCHAR) {
5857                 if (*p == '\t') *p = ' ';
5858                 p++;
5859             }
5860             DisplayInformation(bookOutput);
5861             bookRequested = FALSE;
5862             /* Fall through to parse the current output */
5863         }
5864     }
5865
5866     /*
5867      * Look for machine move.
5868      */
5869     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5870         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5871     {
5872         /* This method is only useful on engines that support ping */
5873         if (cps->lastPing != cps->lastPong) {
5874           if (gameMode == BeginningOfGame) {
5875             /* Extra move from before last new; ignore */
5876             if (appData.debugMode) {
5877                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5878             }
5879           } else {
5880             if (appData.debugMode) {
5881                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5882                         cps->which, gameMode);
5883             }
5884
5885             SendToProgram("undo\n", cps);
5886           }
5887           return;
5888         }
5889
5890         switch (gameMode) {
5891           case BeginningOfGame:
5892             /* Extra move from before last reset; ignore */
5893             if (appData.debugMode) {
5894                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5895             }
5896             return;
5897
5898           case EndOfGame:
5899           case IcsIdle:
5900           default:
5901             /* Extra move after we tried to stop.  The mode test is
5902                not a reliable way of detecting this problem, but it's
5903                the best we can do on engines that don't support ping.
5904             */
5905             if (appData.debugMode) {
5906                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5907                         cps->which, gameMode);
5908             }
5909             SendToProgram("undo\n", cps);
5910             return;
5911
5912           case MachinePlaysWhite:
5913           case IcsPlayingWhite:
5914             machineWhite = TRUE;
5915             break;
5916
5917           case MachinePlaysBlack:
5918           case IcsPlayingBlack:
5919             machineWhite = FALSE;
5920             break;
5921
5922           case TwoMachinesPlay:
5923             machineWhite = (cps->twoMachinesColor[0] == 'w');
5924             break;
5925         }
5926         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5927             if (appData.debugMode) {
5928                 fprintf(debugFP,
5929                         "Ignoring move out of turn by %s, gameMode %d"
5930                         ", forwardMost %d\n",
5931                         cps->which, gameMode, forwardMostMove);
5932             }
5933             return;
5934         }
5935
5936     if (appData.debugMode) { int f = forwardMostMove;
5937         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5938                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5939                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5940     }
5941         if(cps->alphaRank) AlphaRank(machineMove, 4);
5942         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5943                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5944             /* Machine move could not be parsed; ignore it. */
5945             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5946                     machineMove, cps->which);
5947             DisplayError(buf1, 0);
5948             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5949                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5950             if (gameMode == TwoMachinesPlay) {
5951               GameEnds(machineWhite ? BlackWins : WhiteWins,
5952                        buf1, GE_XBOARD);
5953             }
5954             return;
5955         }
5956
5957         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5958         /* So we have to redo legality test with true e.p. status here,  */
5959         /* to make sure an illegal e.p. capture does not slip through,   */
5960         /* to cause a forfeit on a justified illegal-move complaint      */
5961         /* of the opponent.                                              */
5962         if( gameMode==TwoMachinesPlay && appData.testLegality
5963             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5964                                                               ) {
5965            ChessMove moveType;
5966            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5967                              fromY, fromX, toY, toX, promoChar);
5968             if (appData.debugMode) {
5969                 int i;
5970                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5971                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5972                 fprintf(debugFP, "castling rights\n");
5973             }
5974             if(moveType == IllegalMove) {
5975                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5976                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5977                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5978                            buf1, GE_XBOARD);
5979                 return;
5980            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5981            /* [HGM] Kludge to handle engines that send FRC-style castling
5982               when they shouldn't (like TSCP-Gothic) */
5983            switch(moveType) {
5984              case WhiteASideCastleFR:
5985              case BlackASideCastleFR:
5986                toX+=2;
5987                currentMoveString[2]++;
5988                break;
5989              case WhiteHSideCastleFR:
5990              case BlackHSideCastleFR:
5991                toX--;
5992                currentMoveString[2]--;
5993                break;
5994              default: ; // nothing to do, but suppresses warning of pedantic compilers
5995            }
5996         }
5997         hintRequested = FALSE;
5998         lastHint[0] = NULLCHAR;
5999         bookRequested = FALSE;
6000         /* Program may be pondering now */
6001         cps->maybeThinking = TRUE;
6002         if (cps->sendTime == 2) cps->sendTime = 1;
6003         if (cps->offeredDraw) cps->offeredDraw--;
6004
6005 #if ZIPPY
6006         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6007             first.initDone) {
6008           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6009           ics_user_moved = 1;
6010           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6011                 char buf[3*MSG_SIZ];
6012
6013                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6014                         programStats.score / 100.,
6015                         programStats.depth,
6016                         programStats.time / 100.,
6017                         (unsigned int)programStats.nodes,
6018                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6019                         programStats.movelist);
6020                 SendToICS(buf);
6021 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6022           }
6023         }
6024 #endif
6025         /* currentMoveString is set as a side-effect of ParseOneMove */
6026         strcpy(machineMove, currentMoveString);
6027         strcat(machineMove, "\n");
6028         strcpy(moveList[forwardMostMove], machineMove);
6029
6030         /* [AS] Save move info and clear stats for next move */
6031         pvInfoList[ forwardMostMove ].score = programStats.score;
6032         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6033         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6034         ClearProgramStats();
6035         thinkOutput[0] = NULLCHAR;
6036         hiddenThinkOutputState = 0;
6037
6038         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6039
6040         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6041         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6042             int count = 0;
6043
6044             while( count < adjudicateLossPlies ) {
6045                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6046
6047                 if( count & 1 ) {
6048                     score = -score; /* Flip score for winning side */
6049                 }
6050
6051                 if( score > adjudicateLossThreshold ) {
6052                     break;
6053                 }
6054
6055                 count++;
6056             }
6057
6058             if( count >= adjudicateLossPlies ) {
6059                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6060
6061                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6062                     "Xboard adjudication",
6063                     GE_XBOARD );
6064
6065                 return;
6066             }
6067         }
6068
6069         if( gameMode == TwoMachinesPlay ) {
6070           // [HGM] some adjudications useful with buggy engines
6071             int k, count = 0; static int bare = 1;
6072           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6073
6074
6075             if( appData.testLegality )
6076             {   /* [HGM] Some more adjudications for obstinate engines */
6077                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6078                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6079                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6080                 static int moveCount = 6;
6081                 ChessMove result;
6082                 char *reason = NULL;
6083
6084                 /* Count what is on board. */
6085                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6086                 {   ChessSquare p = boards[forwardMostMove][i][j];
6087                     int m=i;
6088
6089                     switch((int) p)
6090                     {   /* count B,N,R and other of each side */
6091                         case WhiteKing:
6092                         case BlackKing:
6093                              NrK++; break; // [HGM] atomic: count Kings
6094                         case WhiteKnight:
6095                              NrWN++; break;
6096                         case WhiteBishop:
6097                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6098                              bishopsColor |= 1 << ((i^j)&1);
6099                              NrWB++; break;
6100                         case BlackKnight:
6101                              NrBN++; break;
6102                         case BlackBishop:
6103                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6104                              bishopsColor |= 1 << ((i^j)&1);
6105                              NrBB++; break;
6106                         case WhiteRook:
6107                              NrWR++; break;
6108                         case BlackRook:
6109                              NrBR++; break;
6110                         case WhiteQueen:
6111                              NrWQ++; break;
6112                         case BlackQueen:
6113                              NrBQ++; break;
6114                         case EmptySquare:
6115                              break;
6116                         case BlackPawn:
6117                              m = 7-i;
6118                         case WhitePawn:
6119                              PawnAdvance += m; NrPawns++;
6120                     }
6121                     NrPieces += (p != EmptySquare);
6122                     NrW += ((int)p < (int)BlackPawn);
6123                     if(gameInfo.variant == VariantXiangqi &&
6124                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6125                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6126                         NrW -= ((int)p < (int)BlackPawn);
6127                     }
6128                 }
6129
6130                 /* Some material-based adjudications that have to be made before stalemate test */
6131                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6132                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6133                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6134                      if(appData.checkMates) {
6135                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6136                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6137                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6138                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6139                          return;
6140                      }
6141                 }
6142
6143                 /* Bare King in Shatranj (loses) or Losers (wins) */
6144                 if( NrW == 1 || NrPieces - NrW == 1) {
6145                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6146                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6147                      if(appData.checkMates) {
6148                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6149                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6151                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6152                          return;
6153                      }
6154                   } else
6155                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6156                   {    /* bare King */
6157                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6158                         if(appData.checkMates) {
6159                             /* but only adjudicate if adjudication enabled */
6160                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6161                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6162                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6163                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6164                             return;
6165                         }
6166                   }
6167                 } else bare = 1;
6168
6169
6170             // don't wait for engine to announce game end if we can judge ourselves
6171             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6172               case MT_CHECK:
6173                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6174                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6175                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6176                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6177                             checkCnt++;
6178                         if(checkCnt >= 2) {
6179                             reason = "Xboard adjudication: 3rd check";
6180                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6181                             break;
6182                         }
6183                     }
6184                 }
6185               case MT_NONE:
6186               default:
6187                 break;
6188               case MT_STALEMATE:
6189               case MT_STAINMATE:
6190                 reason = "Xboard adjudication: Stalemate";
6191                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6192                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6193                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6194                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6195                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6196                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6197                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6198                                                                         EP_CHECKMATE : EP_WINS);
6199                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6200                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6201                 }
6202                 break;
6203               case MT_CHECKMATE:
6204                 reason = "Xboard adjudication: Checkmate";
6205                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6206                 break;
6207             }
6208
6209                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6210                     case EP_STALEMATE:
6211                         result = GameIsDrawn; break;
6212                     case EP_CHECKMATE:
6213                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6214                     case EP_WINS:
6215                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6216                     default:
6217                         result = (ChessMove) 0;
6218                 }
6219                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6220                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6221                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6222                     GameEnds( result, reason, GE_XBOARD );
6223                     return;
6224                 }
6225
6226                 /* Next absolutely insufficient mating material. */
6227                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6228                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6229                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6230                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6231                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6232
6233                      /* always flag draws, for judging claims */
6234                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6235
6236                      if(appData.materialDraws) {
6237                          /* but only adjudicate them if adjudication enabled */
6238                          SendToProgram("force\n", cps->other); // suppress reply
6239                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6240                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6241                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6242                          return;
6243                      }
6244                 }
6245
6246                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6247                 if(NrPieces == 4 &&
6248                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6249                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6250                    || NrWN==2 || NrBN==2     /* KNNK */
6251                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6252                   ) ) {
6253                      if(--moveCount < 0 && appData.trivialDraws)
6254                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6255                           SendToProgram("force\n", cps->other); // suppress reply
6256                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6257                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6258                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6259                           return;
6260                      }
6261                 } else moveCount = 6;
6262             }
6263           }
6264           
6265           if (appData.debugMode) { int i;
6266             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6267                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6268                     appData.drawRepeats);
6269             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6270               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6271             
6272           }
6273
6274                 /* Check for rep-draws */
6275                 count = 0;
6276                 for(k = forwardMostMove-2;
6277                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6278                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6279                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6280                     k-=2)
6281                 {   int rights=0;
6282                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6283                         /* compare castling rights */
6284                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6285                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6286                                 rights++; /* King lost rights, while rook still had them */
6287                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6288                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6289                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6290                                    rights++; /* but at least one rook lost them */
6291                         }
6292                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6293                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6294                                 rights++; 
6295                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6296                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6297                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6298                                    rights++;
6299                         }
6300                         if( rights == 0 && ++count > appData.drawRepeats-2
6301                             && appData.drawRepeats > 1) {
6302                              /* adjudicate after user-specified nr of repeats */
6303                              SendToProgram("force\n", cps->other); // suppress reply
6304                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6305                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6307                                 // [HGM] xiangqi: check for forbidden perpetuals
6308                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6309                                 for(m=forwardMostMove; m>k; m-=2) {
6310                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6311                                         ourPerpetual = 0; // the current mover did not always check
6312                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6313                                         hisPerpetual = 0; // the opponent did not always check
6314                                 }
6315                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6316                                                                         ourPerpetual, hisPerpetual);
6317                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6318                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6319                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6320                                     return;
6321                                 }
6322                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6323                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6324                                 // Now check for perpetual chases
6325                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6326                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6327                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6328                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6329                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6330                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6331                                         return;
6332                                     }
6333                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6334                                         break; // Abort repetition-checking loop.
6335                                 }
6336                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6337                              }
6338                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6339                              return;
6340                         }
6341                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6342                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6343                     }
6344                 }
6345
6346                 /* Now we test for 50-move draws. Determine ply count */
6347                 count = forwardMostMove;
6348                 /* look for last irreversble move */
6349                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6350                     count--;
6351                 /* if we hit starting position, add initial plies */
6352                 if( count == backwardMostMove )
6353                     count -= initialRulePlies;
6354                 count = forwardMostMove - count;
6355                 if( count >= 100)
6356                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6357                          /* this is used to judge if draw claims are legal */
6358                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6363                          return;
6364                 }
6365
6366                 /* if draw offer is pending, treat it as a draw claim
6367                  * when draw condition present, to allow engines a way to
6368                  * claim draws before making their move to avoid a race
6369                  * condition occurring after their move
6370                  */
6371                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6372                          char *p = NULL;
6373                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6374                              p = "Draw claim: 50-move rule";
6375                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6376                              p = "Draw claim: 3-fold repetition";
6377                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6378                              p = "Draw claim: insufficient mating material";
6379                          if( p != NULL ) {
6380                              SendToProgram("force\n", cps->other); // suppress reply
6381                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6382                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6383                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6384                              return;
6385                          }
6386                 }
6387
6388
6389                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6390                     SendToProgram("force\n", cps->other); // suppress reply
6391                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6392                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6393
6394                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6395
6396                     return;
6397                 }
6398         }
6399
6400         bookHit = NULL;
6401         if (gameMode == TwoMachinesPlay) {
6402             /* [HGM] relaying draw offers moved to after reception of move */
6403             /* and interpreting offer as claim if it brings draw condition */
6404             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6405                 SendToProgram("draw\n", cps->other);
6406             }
6407             if (cps->other->sendTime) {
6408                 SendTimeRemaining(cps->other,
6409                                   cps->other->twoMachinesColor[0] == 'w');
6410             }
6411             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6412             if (firstMove && !bookHit) {
6413                 firstMove = FALSE;
6414                 if (cps->other->useColors) {
6415                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6416                 }
6417                 SendToProgram("go\n", cps->other);
6418             }
6419             cps->other->maybeThinking = TRUE;
6420         }
6421
6422         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6423
6424         if (!pausing && appData.ringBellAfterMoves) {
6425             RingBell();
6426         }
6427
6428         /*
6429          * Reenable menu items that were disabled while
6430          * machine was thinking
6431          */
6432         if (gameMode != TwoMachinesPlay)
6433             SetUserThinkingEnables();
6434
6435         // [HGM] book: after book hit opponent has received move and is now in force mode
6436         // force the book reply into it, and then fake that it outputted this move by jumping
6437         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6438         if(bookHit) {
6439                 static char bookMove[MSG_SIZ]; // a bit generous?
6440
6441                 strcpy(bookMove, "move ");
6442                 strcat(bookMove, bookHit);
6443                 message = bookMove;
6444                 cps = cps->other;
6445                 programStats.nodes = programStats.depth = programStats.time =
6446                 programStats.score = programStats.got_only_move = 0;
6447                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6448
6449                 if(cps->lastPing != cps->lastPong) {
6450                     savedMessage = message; // args for deferred call
6451                     savedState = cps;
6452                     ScheduleDelayedEvent(DeferredBookMove, 10);
6453                     return;
6454                 }
6455                 goto FakeBookMove;
6456         }
6457
6458         return;
6459     }
6460
6461     /* Set special modes for chess engines.  Later something general
6462      *  could be added here; for now there is just one kludge feature,
6463      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6464      *  when "xboard" is given as an interactive command.
6465      */
6466     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6467         cps->useSigint = FALSE;
6468         cps->useSigterm = FALSE;
6469     }
6470     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6471       ParseFeatures(message+8, cps);
6472       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6473     }
6474
6475     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6476      * want this, I was asked to put it in, and obliged.
6477      */
6478     if (!strncmp(message, "setboard ", 9)) {
6479         Board initial_position;
6480
6481         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6482
6483         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6484             DisplayError(_("Bad FEN received from engine"), 0);
6485             return ;
6486         } else {
6487            Reset(TRUE, FALSE);
6488            CopyBoard(boards[0], initial_position);
6489            initialRulePlies = FENrulePlies;
6490            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6491            else gameMode = MachinePlaysBlack;
6492            DrawPosition(FALSE, boards[currentMove]);
6493         }
6494         return;
6495     }
6496
6497     /*
6498      * Look for communication commands
6499      */
6500     if (!strncmp(message, "telluser ", 9)) {
6501         DisplayNote(message + 9);
6502         return;
6503     }
6504     if (!strncmp(message, "tellusererror ", 14)) {
6505         DisplayError(message + 14, 0);
6506         return;
6507     }
6508     if (!strncmp(message, "tellopponent ", 13)) {
6509       if (appData.icsActive) {
6510         if (loggedOn) {
6511           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6512           SendToICS(buf1);
6513         }
6514       } else {
6515         DisplayNote(message + 13);
6516       }
6517       return;
6518     }
6519     if (!strncmp(message, "tellothers ", 11)) {
6520       if (appData.icsActive) {
6521         if (loggedOn) {
6522           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6523           SendToICS(buf1);
6524         }
6525       }
6526       return;
6527     }
6528     if (!strncmp(message, "tellall ", 8)) {
6529       if (appData.icsActive) {
6530         if (loggedOn) {
6531           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6532           SendToICS(buf1);
6533         }
6534       } else {
6535         DisplayNote(message + 8);
6536       }
6537       return;
6538     }
6539     if (strncmp(message, "warning", 7) == 0) {
6540         /* Undocumented feature, use tellusererror in new code */
6541         DisplayError(message, 0);
6542         return;
6543     }
6544     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6545         strcpy(realname, cps->tidy);
6546         strcat(realname, " query");
6547         AskQuestion(realname, buf2, buf1, cps->pr);
6548         return;
6549     }
6550     /* Commands from the engine directly to ICS.  We don't allow these to be
6551      *  sent until we are logged on. Crafty kibitzes have been known to
6552      *  interfere with the login process.
6553      */
6554     if (loggedOn) {
6555         if (!strncmp(message, "tellics ", 8)) {
6556             SendToICS(message + 8);
6557             SendToICS("\n");
6558             return;
6559         }
6560         if (!strncmp(message, "tellicsnoalias ", 15)) {
6561             SendToICS(ics_prefix);
6562             SendToICS(message + 15);
6563             SendToICS("\n");
6564             return;
6565         }
6566         /* The following are for backward compatibility only */
6567         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6568             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6569             SendToICS(ics_prefix);
6570             SendToICS(message);
6571             SendToICS("\n");
6572             return;
6573         }
6574     }
6575     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6576         return;
6577     }
6578     /*
6579      * If the move is illegal, cancel it and redraw the board.
6580      * Also deal with other error cases.  Matching is rather loose
6581      * here to accommodate engines written before the spec.
6582      */
6583     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6584         strncmp(message, "Error", 5) == 0) {
6585         if (StrStr(message, "name") ||
6586             StrStr(message, "rating") || StrStr(message, "?") ||
6587             StrStr(message, "result") || StrStr(message, "board") ||
6588             StrStr(message, "bk") || StrStr(message, "computer") ||
6589             StrStr(message, "variant") || StrStr(message, "hint") ||
6590             StrStr(message, "random") || StrStr(message, "depth") ||
6591             StrStr(message, "accepted")) {
6592             return;
6593         }
6594         if (StrStr(message, "protover")) {
6595           /* Program is responding to input, so it's apparently done
6596              initializing, and this error message indicates it is
6597              protocol version 1.  So we don't need to wait any longer
6598              for it to initialize and send feature commands. */
6599           FeatureDone(cps, 1);
6600           cps->protocolVersion = 1;
6601           return;
6602         }
6603         cps->maybeThinking = FALSE;
6604
6605         if (StrStr(message, "draw")) {
6606             /* Program doesn't have "draw" command */
6607             cps->sendDrawOffers = 0;
6608             return;
6609         }
6610         if (cps->sendTime != 1 &&
6611             (StrStr(message, "time") || StrStr(message, "otim"))) {
6612           /* Program apparently doesn't have "time" or "otim" command */
6613           cps->sendTime = 0;
6614           return;
6615         }
6616         if (StrStr(message, "analyze")) {
6617             cps->analysisSupport = FALSE;
6618             cps->analyzing = FALSE;
6619             Reset(FALSE, TRUE);
6620             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6621             DisplayError(buf2, 0);
6622             return;
6623         }
6624         if (StrStr(message, "(no matching move)st")) {
6625           /* Special kludge for GNU Chess 4 only */
6626           cps->stKludge = TRUE;
6627           SendTimeControl(cps, movesPerSession, timeControl,
6628                           timeIncrement, appData.searchDepth,
6629                           searchTime);
6630           return;
6631         }
6632         if (StrStr(message, "(no matching move)sd")) {
6633           /* Special kludge for GNU Chess 4 only */
6634           cps->sdKludge = TRUE;
6635           SendTimeControl(cps, movesPerSession, timeControl,
6636                           timeIncrement, appData.searchDepth,
6637                           searchTime);
6638           return;
6639         }
6640         if (!StrStr(message, "llegal")) {
6641             return;
6642         }
6643         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6644             gameMode == IcsIdle) return;
6645         if (forwardMostMove <= backwardMostMove) return;
6646         if (pausing) PauseEvent();
6647       if(appData.forceIllegal) {
6648             // [HGM] illegal: machine refused move; force position after move into it
6649           SendToProgram("force\n", cps);
6650           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6651                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6652                 // when black is to move, while there might be nothing on a2 or black
6653                 // might already have the move. So send the board as if white has the move.
6654                 // But first we must change the stm of the engine, as it refused the last move
6655                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6656                 if(WhiteOnMove(forwardMostMove)) {
6657                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6658                     SendBoard(cps, forwardMostMove); // kludgeless board
6659                 } else {
6660                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6661                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6662                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6663                 }
6664           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6665             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6666                  gameMode == TwoMachinesPlay)
6667               SendToProgram("go\n", cps);
6668             return;
6669       } else
6670         if (gameMode == PlayFromGameFile) {
6671             /* Stop reading this game file */
6672             gameMode = EditGame;
6673             ModeHighlight();
6674         }
6675         currentMove = --forwardMostMove;
6676         DisplayMove(currentMove-1); /* before DisplayMoveError */
6677         SwitchClocks();
6678         DisplayBothClocks();
6679         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6680                 parseList[currentMove], cps->which);
6681         DisplayMoveError(buf1);
6682         DrawPosition(FALSE, boards[currentMove]);
6683
6684         /* [HGM] illegal-move claim should forfeit game when Xboard */
6685         /* only passes fully legal moves                            */
6686         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6687             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6688                                 "False illegal-move claim", GE_XBOARD );
6689         }
6690         return;
6691     }
6692     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6693         /* Program has a broken "time" command that
6694            outputs a string not ending in newline.
6695            Don't use it. */
6696         cps->sendTime = 0;
6697     }
6698
6699     /*
6700      * If chess program startup fails, exit with an error message.
6701      * Attempts to recover here are futile.
6702      */
6703     if ((StrStr(message, "unknown host") != NULL)
6704         || (StrStr(message, "No remote directory") != NULL)
6705         || (StrStr(message, "not found") != NULL)
6706         || (StrStr(message, "No such file") != NULL)
6707         || (StrStr(message, "can't alloc") != NULL)
6708         || (StrStr(message, "Permission denied") != NULL)) {
6709
6710         cps->maybeThinking = FALSE;
6711         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6712                 cps->which, cps->program, cps->host, message);
6713         RemoveInputSource(cps->isr);
6714         DisplayFatalError(buf1, 0, 1);
6715         return;
6716     }
6717
6718     /*
6719      * Look for hint output
6720      */
6721     if (sscanf(message, "Hint: %s", buf1) == 1) {
6722         if (cps == &first && hintRequested) {
6723             hintRequested = FALSE;
6724             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6725                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6726                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6727                                     PosFlags(forwardMostMove),
6728                                     fromY, fromX, toY, toX, promoChar, buf1);
6729                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6730                 DisplayInformation(buf2);
6731             } else {
6732                 /* Hint move could not be parsed!? */
6733               snprintf(buf2, sizeof(buf2),
6734                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6735                         buf1, cps->which);
6736                 DisplayError(buf2, 0);
6737             }
6738         } else {
6739             strcpy(lastHint, buf1);
6740         }
6741         return;
6742     }
6743
6744     /*
6745      * Ignore other messages if game is not in progress
6746      */
6747     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6748         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6749
6750     /*
6751      * look for win, lose, draw, or draw offer
6752      */
6753     if (strncmp(message, "1-0", 3) == 0) {
6754         char *p, *q, *r = "";
6755         p = strchr(message, '{');
6756         if (p) {
6757             q = strchr(p, '}');
6758             if (q) {
6759                 *q = NULLCHAR;
6760                 r = p + 1;
6761             }
6762         }
6763         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6764         return;
6765     } else if (strncmp(message, "0-1", 3) == 0) {
6766         char *p, *q, *r = "";
6767         p = strchr(message, '{');
6768         if (p) {
6769             q = strchr(p, '}');
6770             if (q) {
6771                 *q = NULLCHAR;
6772                 r = p + 1;
6773             }
6774         }
6775         /* Kludge for Arasan 4.1 bug */
6776         if (strcmp(r, "Black resigns") == 0) {
6777             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6778             return;
6779         }
6780         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6781         return;
6782     } else if (strncmp(message, "1/2", 3) == 0) {
6783         char *p, *q, *r = "";
6784         p = strchr(message, '{');
6785         if (p) {
6786             q = strchr(p, '}');
6787             if (q) {
6788                 *q = NULLCHAR;
6789                 r = p + 1;
6790             }
6791         }
6792
6793         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6794         return;
6795
6796     } else if (strncmp(message, "White resign", 12) == 0) {
6797         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6798         return;
6799     } else if (strncmp(message, "Black resign", 12) == 0) {
6800         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6801         return;
6802     } else if (strncmp(message, "White matches", 13) == 0 ||
6803                strncmp(message, "Black matches", 13) == 0   ) {
6804         /* [HGM] ignore GNUShogi noises */
6805         return;
6806     } else if (strncmp(message, "White", 5) == 0 &&
6807                message[5] != '(' &&
6808                StrStr(message, "Black") == NULL) {
6809         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6810         return;
6811     } else if (strncmp(message, "Black", 5) == 0 &&
6812                message[5] != '(') {
6813         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6814         return;
6815     } else if (strcmp(message, "resign") == 0 ||
6816                strcmp(message, "computer resigns") == 0) {
6817         switch (gameMode) {
6818           case MachinePlaysBlack:
6819           case IcsPlayingBlack:
6820             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6821             break;
6822           case MachinePlaysWhite:
6823           case IcsPlayingWhite:
6824             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6825             break;
6826           case TwoMachinesPlay:
6827             if (cps->twoMachinesColor[0] == 'w')
6828               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6829             else
6830               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6831             break;
6832           default:
6833             /* can't happen */
6834             break;
6835         }
6836         return;
6837     } else if (strncmp(message, "opponent mates", 14) == 0) {
6838         switch (gameMode) {
6839           case MachinePlaysBlack:
6840           case IcsPlayingBlack:
6841             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6842             break;
6843           case MachinePlaysWhite:
6844           case IcsPlayingWhite:
6845             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6846             break;
6847           case TwoMachinesPlay:
6848             if (cps->twoMachinesColor[0] == 'w')
6849               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6850             else
6851               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6852             break;
6853           default:
6854             /* can't happen */
6855             break;
6856         }
6857         return;
6858     } else if (strncmp(message, "computer mates", 14) == 0) {
6859         switch (gameMode) {
6860           case MachinePlaysBlack:
6861           case IcsPlayingBlack:
6862             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6863             break;
6864           case MachinePlaysWhite:
6865           case IcsPlayingWhite:
6866             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6867             break;
6868           case TwoMachinesPlay:
6869             if (cps->twoMachinesColor[0] == 'w')
6870               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6871             else
6872               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6873             break;
6874           default:
6875             /* can't happen */
6876             break;
6877         }
6878         return;
6879     } else if (strncmp(message, "checkmate", 9) == 0) {
6880         if (WhiteOnMove(forwardMostMove)) {
6881             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6882         } else {
6883             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6884         }
6885         return;
6886     } else if (strstr(message, "Draw") != NULL ||
6887                strstr(message, "game is a draw") != NULL) {
6888         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6889         return;
6890     } else if (strstr(message, "offer") != NULL &&
6891                strstr(message, "draw") != NULL) {
6892 #if ZIPPY
6893         if (appData.zippyPlay && first.initDone) {
6894             /* Relay offer to ICS */
6895             SendToICS(ics_prefix);
6896             SendToICS("draw\n");
6897         }
6898 #endif
6899         cps->offeredDraw = 2; /* valid until this engine moves twice */
6900         if (gameMode == TwoMachinesPlay) {
6901             if (cps->other->offeredDraw) {
6902                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6903             /* [HGM] in two-machine mode we delay relaying draw offer      */
6904             /* until after we also have move, to see if it is really claim */
6905             }
6906         } else if (gameMode == MachinePlaysWhite ||
6907                    gameMode == MachinePlaysBlack) {
6908           if (userOfferedDraw) {
6909             DisplayInformation(_("Machine accepts your draw offer"));
6910             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6911           } else {
6912             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6913           }
6914         }
6915     }
6916
6917
6918     /*
6919      * Look for thinking output
6920      */
6921     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6922           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6923                                 ) {
6924         int plylev, mvleft, mvtot, curscore, time;
6925         char mvname[MOVE_LEN];
6926         u64 nodes; // [DM]
6927         char plyext;
6928         int ignore = FALSE;
6929         int prefixHint = FALSE;
6930         mvname[0] = NULLCHAR;
6931
6932         switch (gameMode) {
6933           case MachinePlaysBlack:
6934           case IcsPlayingBlack:
6935             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6936             break;
6937           case MachinePlaysWhite:
6938           case IcsPlayingWhite:
6939             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6940             break;
6941           case AnalyzeMode:
6942           case AnalyzeFile:
6943             break;
6944           case IcsObserving: /* [DM] icsEngineAnalyze */
6945             if (!appData.icsEngineAnalyze) ignore = TRUE;
6946             break;
6947           case TwoMachinesPlay:
6948             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6949                 ignore = TRUE;
6950             }
6951             break;
6952           default:
6953             ignore = TRUE;
6954             break;
6955         }
6956
6957         if (!ignore) {
6958             buf1[0] = NULLCHAR;
6959             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6960                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6961
6962                 if (plyext != ' ' && plyext != '\t') {
6963                     time *= 100;
6964                 }
6965
6966                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6967                 if( cps->scoreIsAbsolute && 
6968                     ( gameMode == MachinePlaysBlack ||
6969                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6970                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6971                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6972                      !WhiteOnMove(currentMove)
6973                     ) )
6974                 {
6975                     curscore = -curscore;
6976                 }
6977
6978
6979                 programStats.depth = plylev;
6980                 programStats.nodes = nodes;
6981                 programStats.time = time;
6982                 programStats.score = curscore;
6983                 programStats.got_only_move = 0;
6984
6985                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6986                         int ticklen;
6987
6988                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6989                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6990                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6991                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6992                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6993                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6994                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6995                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6996                 }
6997
6998                 /* Buffer overflow protection */
6999                 if (buf1[0] != NULLCHAR) {
7000                     if (strlen(buf1) >= sizeof(programStats.movelist)
7001                         && appData.debugMode) {
7002                         fprintf(debugFP,
7003                                 "PV is too long; using the first %u bytes.\n",
7004                                 (unsigned) sizeof(programStats.movelist) - 1);
7005                     }
7006
7007                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7008                 } else {
7009                     sprintf(programStats.movelist, " no PV\n");
7010                 }
7011
7012                 if (programStats.seen_stat) {
7013                     programStats.ok_to_send = 1;
7014                 }
7015
7016                 if (strchr(programStats.movelist, '(') != NULL) {
7017                     programStats.line_is_book = 1;
7018                     programStats.nr_moves = 0;
7019                     programStats.moves_left = 0;
7020                 } else {
7021                     programStats.line_is_book = 0;
7022                 }
7023
7024                 SendProgramStatsToFrontend( cps, &programStats );
7025
7026                 /*
7027                     [AS] Protect the thinkOutput buffer from overflow... this
7028                     is only useful if buf1 hasn't overflowed first!
7029                 */
7030                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7031                         plylev,
7032                         (gameMode == TwoMachinesPlay ?
7033                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7034                         ((double) curscore) / 100.0,
7035                         prefixHint ? lastHint : "",
7036                         prefixHint ? " " : "" );
7037
7038                 if( buf1[0] != NULLCHAR ) {
7039                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7040
7041                     if( strlen(buf1) > max_len ) {
7042                         if( appData.debugMode) {
7043                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7044                         }
7045                         buf1[max_len+1] = '\0';
7046                     }
7047
7048                     strcat( thinkOutput, buf1 );
7049                 }
7050
7051                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7052                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7053                     DisplayMove(currentMove - 1);
7054                 }
7055                 return;
7056
7057             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7058                 /* crafty (9.25+) says "(only move) <move>"
7059                  * if there is only 1 legal move
7060                  */
7061                 sscanf(p, "(only move) %s", buf1);
7062                 sprintf(thinkOutput, "%s (only move)", buf1);
7063                 sprintf(programStats.movelist, "%s (only move)", buf1);
7064                 programStats.depth = 1;
7065                 programStats.nr_moves = 1;
7066                 programStats.moves_left = 1;
7067                 programStats.nodes = 1;
7068                 programStats.time = 1;
7069                 programStats.got_only_move = 1;
7070
7071                 /* Not really, but we also use this member to
7072                    mean "line isn't going to change" (Crafty
7073                    isn't searching, so stats won't change) */
7074                 programStats.line_is_book = 1;
7075
7076                 SendProgramStatsToFrontend( cps, &programStats );
7077
7078                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7079                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7080                     DisplayMove(currentMove - 1);
7081                 }
7082                 return;
7083             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7084                               &time, &nodes, &plylev, &mvleft,
7085                               &mvtot, mvname) >= 5) {
7086                 /* The stat01: line is from Crafty (9.29+) in response
7087                    to the "." command */
7088                 programStats.seen_stat = 1;
7089                 cps->maybeThinking = TRUE;
7090
7091                 if (programStats.got_only_move || !appData.periodicUpdates)
7092                   return;
7093
7094                 programStats.depth = plylev;
7095                 programStats.time = time;
7096                 programStats.nodes = nodes;
7097                 programStats.moves_left = mvleft;
7098                 programStats.nr_moves = mvtot;
7099                 strcpy(programStats.move_name, mvname);
7100                 programStats.ok_to_send = 1;
7101                 programStats.movelist[0] = '\0';
7102
7103                 SendProgramStatsToFrontend( cps, &programStats );
7104
7105                 return;
7106
7107             } else if (strncmp(message,"++",2) == 0) {
7108                 /* Crafty 9.29+ outputs this */
7109                 programStats.got_fail = 2;
7110                 return;
7111
7112             } else if (strncmp(message,"--",2) == 0) {
7113                 /* Crafty 9.29+ outputs this */
7114                 programStats.got_fail = 1;
7115                 return;
7116
7117             } else if (thinkOutput[0] != NULLCHAR &&
7118                        strncmp(message, "    ", 4) == 0) {
7119                 unsigned message_len;
7120
7121                 p = message;
7122                 while (*p && *p == ' ') p++;
7123
7124                 message_len = strlen( p );
7125
7126                 /* [AS] Avoid buffer overflow */
7127                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7128                     strcat(thinkOutput, " ");
7129                     strcat(thinkOutput, p);
7130                 }
7131
7132                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7133                     strcat(programStats.movelist, " ");
7134                     strcat(programStats.movelist, p);
7135                 }
7136
7137                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7138                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7139                     DisplayMove(currentMove - 1);
7140                 }
7141                 return;
7142             }
7143         }
7144         else {
7145             buf1[0] = NULLCHAR;
7146
7147             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7148                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7149             {
7150                 ChessProgramStats cpstats;
7151
7152                 if (plyext != ' ' && plyext != '\t') {
7153                     time *= 100;
7154                 }
7155
7156                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7157                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7158                     curscore = -curscore;
7159                 }
7160
7161                 cpstats.depth = plylev;
7162                 cpstats.nodes = nodes;
7163                 cpstats.time = time;
7164                 cpstats.score = curscore;
7165                 cpstats.got_only_move = 0;
7166                 cpstats.movelist[0] = '\0';
7167
7168                 if (buf1[0] != NULLCHAR) {
7169                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7170                 }
7171
7172                 cpstats.ok_to_send = 0;
7173                 cpstats.line_is_book = 0;
7174                 cpstats.nr_moves = 0;
7175                 cpstats.moves_left = 0;
7176
7177                 SendProgramStatsToFrontend( cps, &cpstats );
7178             }
7179         }
7180     }
7181 }
7182
7183
7184 /* Parse a game score from the character string "game", and
7185    record it as the history of the current game.  The game
7186    score is NOT assumed to start from the standard position.
7187    The display is not updated in any way.
7188    */
7189 void
7190 ParseGameHistory(game)
7191      char *game;
7192 {
7193     ChessMove moveType;
7194     int fromX, fromY, toX, toY, boardIndex;
7195     char promoChar;
7196     char *p, *q;
7197     char buf[MSG_SIZ];
7198
7199     if (appData.debugMode)
7200       fprintf(debugFP, "Parsing game history: %s\n", game);
7201
7202     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7203     gameInfo.site = StrSave(appData.icsHost);
7204     gameInfo.date = PGNDate();
7205     gameInfo.round = StrSave("-");
7206
7207     /* Parse out names of players */
7208     while (*game == ' ') game++;
7209     p = buf;
7210     while (*game != ' ') *p++ = *game++;
7211     *p = NULLCHAR;
7212     gameInfo.white = StrSave(buf);
7213     while (*game == ' ') game++;
7214     p = buf;
7215     while (*game != ' ' && *game != '\n') *p++ = *game++;
7216     *p = NULLCHAR;
7217     gameInfo.black = StrSave(buf);
7218
7219     /* Parse moves */
7220     boardIndex = blackPlaysFirst ? 1 : 0;
7221     yynewstr(game);
7222     for (;;) {
7223         yyboardindex = boardIndex;
7224         moveType = (ChessMove) yylex();
7225         switch (moveType) {
7226           case IllegalMove:             /* maybe suicide chess, etc. */
7227   if (appData.debugMode) {
7228     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7229     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7230     setbuf(debugFP, NULL);
7231   }
7232           case WhitePromotionChancellor:
7233           case BlackPromotionChancellor:
7234           case WhitePromotionArchbishop:
7235           case BlackPromotionArchbishop:
7236           case WhitePromotionQueen:
7237           case BlackPromotionQueen:
7238           case WhitePromotionRook:
7239           case BlackPromotionRook:
7240           case WhitePromotionBishop:
7241           case BlackPromotionBishop:
7242           case WhitePromotionKnight:
7243           case BlackPromotionKnight:
7244           case WhitePromotionKing:
7245           case BlackPromotionKing:
7246           case NormalMove:
7247           case WhiteCapturesEnPassant:
7248           case BlackCapturesEnPassant:
7249           case WhiteKingSideCastle:
7250           case WhiteQueenSideCastle:
7251           case BlackKingSideCastle:
7252           case BlackQueenSideCastle:
7253           case WhiteKingSideCastleWild:
7254           case WhiteQueenSideCastleWild:
7255           case BlackKingSideCastleWild:
7256           case BlackQueenSideCastleWild:
7257           /* PUSH Fabien */
7258           case WhiteHSideCastleFR:
7259           case WhiteASideCastleFR:
7260           case BlackHSideCastleFR:
7261           case BlackASideCastleFR:
7262           /* POP Fabien */
7263             fromX = currentMoveString[0] - AAA;
7264             fromY = currentMoveString[1] - ONE;
7265             toX = currentMoveString[2] - AAA;
7266             toY = currentMoveString[3] - ONE;
7267             promoChar = currentMoveString[4];
7268             break;
7269           case WhiteDrop:
7270           case BlackDrop:
7271             fromX = moveType == WhiteDrop ?
7272               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7273             (int) CharToPiece(ToLower(currentMoveString[0]));
7274             fromY = DROP_RANK;
7275             toX = currentMoveString[2] - AAA;
7276             toY = currentMoveString[3] - ONE;
7277             promoChar = NULLCHAR;
7278             break;
7279           case AmbiguousMove:
7280             /* bug? */
7281             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7282   if (appData.debugMode) {
7283     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7284     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7285     setbuf(debugFP, NULL);
7286   }
7287             DisplayError(buf, 0);
7288             return;
7289           case ImpossibleMove:
7290             /* bug? */
7291             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7292   if (appData.debugMode) {
7293     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7294     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7295     setbuf(debugFP, NULL);
7296   }
7297             DisplayError(buf, 0);
7298             return;
7299           case (ChessMove) 0:   /* end of file */
7300             if (boardIndex < backwardMostMove) {
7301                 /* Oops, gap.  How did that happen? */
7302                 DisplayError(_("Gap in move list"), 0);
7303                 return;
7304             }
7305             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7306             if (boardIndex > forwardMostMove) {
7307                 forwardMostMove = boardIndex;
7308             }
7309             return;
7310           case ElapsedTime:
7311             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7312                 strcat(parseList[boardIndex-1], " ");
7313                 strcat(parseList[boardIndex-1], yy_text);
7314             }
7315             continue;
7316           case Comment:
7317           case PGNTag:
7318           case NAG:
7319           default:
7320             /* ignore */
7321             continue;
7322           case WhiteWins:
7323           case BlackWins:
7324           case GameIsDrawn:
7325           case GameUnfinished:
7326             if (gameMode == IcsExamining) {
7327                 if (boardIndex < backwardMostMove) {
7328                     /* Oops, gap.  How did that happen? */
7329                     return;
7330                 }
7331                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7332                 return;
7333             }
7334             gameInfo.result = moveType;
7335             p = strchr(yy_text, '{');
7336             if (p == NULL) p = strchr(yy_text, '(');
7337             if (p == NULL) {
7338                 p = yy_text;
7339                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7340             } else {
7341                 q = strchr(p, *p == '{' ? '}' : ')');
7342                 if (q != NULL) *q = NULLCHAR;
7343                 p++;
7344             }
7345             gameInfo.resultDetails = StrSave(p);
7346             continue;
7347         }
7348         if (boardIndex >= forwardMostMove &&
7349             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7350             backwardMostMove = blackPlaysFirst ? 1 : 0;
7351             return;
7352         }
7353         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7354                                  fromY, fromX, toY, toX, promoChar,
7355                                  parseList[boardIndex]);
7356         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7357         /* currentMoveString is set as a side-effect of yylex */
7358         strcpy(moveList[boardIndex], currentMoveString);
7359         strcat(moveList[boardIndex], "\n");
7360         boardIndex++;
7361         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7362         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7363           case MT_NONE:
7364           case MT_STALEMATE:
7365           default:
7366             break;
7367           case MT_CHECK:
7368             if(gameInfo.variant != VariantShogi)
7369                 strcat(parseList[boardIndex - 1], "+");
7370             break;
7371           case MT_CHECKMATE:
7372           case MT_STAINMATE:
7373             strcat(parseList[boardIndex - 1], "#");
7374             break;
7375         }
7376     }
7377 }
7378
7379
7380 /* Apply a move to the given board  */
7381 void
7382 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7383      int fromX, fromY, toX, toY;
7384      int promoChar;
7385      Board board;
7386 {
7387   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7388
7389     /* [HGM] compute & store e.p. status and castling rights for new position */
7390     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7391     { int i;
7392
7393       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7394       oldEP = (signed char)board[EP_STATUS];
7395       board[EP_STATUS] = EP_NONE;
7396
7397       if( board[toY][toX] != EmptySquare ) 
7398            board[EP_STATUS] = EP_CAPTURE;  
7399
7400       if( board[fromY][fromX] == WhitePawn ) {
7401            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7402                board[EP_STATUS] = EP_PAWN_MOVE;
7403            if( toY-fromY==2) {
7404                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7405                         gameInfo.variant != VariantBerolina || toX < fromX)
7406                       board[EP_STATUS] = toX | berolina;
7407                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7408                   gameInfo.variant != VariantBerolina || toX > fromX) 
7409                  board[EP_STATUS] = toX;
7410            }
7411       } else
7412       if( board[fromY][fromX] == BlackPawn ) {
7413            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7414                board[EP_STATUS] = EP_PAWN_MOVE; 
7415            if( toY-fromY== -2) {
7416                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7417                         gameInfo.variant != VariantBerolina || toX < fromX)
7418                       board[EP_STATUS] = toX | berolina;
7419                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7420                         gameInfo.variant != VariantBerolina || toX > fromX) 
7421                       board[EP_STATUS] = toX;
7422            }
7423        }
7424
7425        for(i=0; i<nrCastlingRights; i++) {
7426            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7427               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7428              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7429        }
7430
7431     }
7432
7433   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7434   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7435        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7436
7437   if (fromX == toX && fromY == toY) return;
7438
7439   if (fromY == DROP_RANK) {
7440         /* must be first */
7441         piece = board[toY][toX] = (ChessSquare) fromX;
7442   } else {
7443      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7444      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7445      if(gameInfo.variant == VariantKnightmate)
7446          king += (int) WhiteUnicorn - (int) WhiteKing;
7447
7448     /* Code added by Tord: */
7449     /* FRC castling assumed when king captures friendly rook. */
7450     if (board[fromY][fromX] == WhiteKing &&
7451              board[toY][toX] == WhiteRook) {
7452       board[fromY][fromX] = EmptySquare;
7453       board[toY][toX] = EmptySquare;
7454       if(toX > fromX) {
7455         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7456       } else {
7457         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7458       }
7459     } else if (board[fromY][fromX] == BlackKing &&
7460                board[toY][toX] == BlackRook) {
7461       board[fromY][fromX] = EmptySquare;
7462       board[toY][toX] = EmptySquare;
7463       if(toX > fromX) {
7464         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7465       } else {
7466         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7467       }
7468     /* End of code added by Tord */
7469
7470     } else if (board[fromY][fromX] == king
7471         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7472         && toY == fromY && toX > fromX+1) {
7473         board[fromY][fromX] = EmptySquare;
7474         board[toY][toX] = king;
7475         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7476         board[fromY][BOARD_RGHT-1] = EmptySquare;
7477     } else if (board[fromY][fromX] == king
7478         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7479                && toY == fromY && toX < fromX-1) {
7480         board[fromY][fromX] = EmptySquare;
7481         board[toY][toX] = king;
7482         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7483         board[fromY][BOARD_LEFT] = EmptySquare;
7484     } else if (board[fromY][fromX] == WhitePawn
7485                && toY == BOARD_HEIGHT-1
7486                && gameInfo.variant != VariantXiangqi
7487                ) {
7488         /* white pawn promotion */
7489         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7490         if (board[toY][toX] == EmptySquare) {
7491             board[toY][toX] = WhiteQueen;
7492         }
7493         if(gameInfo.variant==VariantBughouse ||
7494            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7495             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7496         board[fromY][fromX] = EmptySquare;
7497     } else if ((fromY == BOARD_HEIGHT-4)
7498                && (toX != fromX)
7499                && gameInfo.variant != VariantXiangqi
7500                && gameInfo.variant != VariantBerolina
7501                && (board[fromY][fromX] == WhitePawn)
7502                && (board[toY][toX] == EmptySquare)) {
7503         board[fromY][fromX] = EmptySquare;
7504         board[toY][toX] = WhitePawn;
7505         captured = board[toY - 1][toX];
7506         board[toY - 1][toX] = EmptySquare;
7507     } else if ((fromY == BOARD_HEIGHT-4)
7508                && (toX == fromX)
7509                && gameInfo.variant == VariantBerolina
7510                && (board[fromY][fromX] == WhitePawn)
7511                && (board[toY][toX] == EmptySquare)) {
7512         board[fromY][fromX] = EmptySquare;
7513         board[toY][toX] = WhitePawn;
7514         if(oldEP & EP_BEROLIN_A) {
7515                 captured = board[fromY][fromX-1];
7516                 board[fromY][fromX-1] = EmptySquare;
7517         }else{  captured = board[fromY][fromX+1];
7518                 board[fromY][fromX+1] = EmptySquare;
7519         }
7520     } else if (board[fromY][fromX] == king
7521         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7522                && toY == fromY && toX > fromX+1) {
7523         board[fromY][fromX] = EmptySquare;
7524         board[toY][toX] = king;
7525         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7526         board[fromY][BOARD_RGHT-1] = EmptySquare;
7527     } else if (board[fromY][fromX] == king
7528         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7529                && toY == fromY && toX < fromX-1) {
7530         board[fromY][fromX] = EmptySquare;
7531         board[toY][toX] = king;
7532         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7533         board[fromY][BOARD_LEFT] = EmptySquare;
7534     } else if (fromY == 7 && fromX == 3
7535                && board[fromY][fromX] == BlackKing
7536                && toY == 7 && toX == 5) {
7537         board[fromY][fromX] = EmptySquare;
7538         board[toY][toX] = BlackKing;
7539         board[fromY][7] = EmptySquare;
7540         board[toY][4] = BlackRook;
7541     } else if (fromY == 7 && fromX == 3
7542                && board[fromY][fromX] == BlackKing
7543                && toY == 7 && toX == 1) {
7544         board[fromY][fromX] = EmptySquare;
7545         board[toY][toX] = BlackKing;
7546         board[fromY][0] = EmptySquare;
7547         board[toY][2] = BlackRook;
7548     } else if (board[fromY][fromX] == BlackPawn
7549                && toY == 0
7550                && gameInfo.variant != VariantXiangqi
7551                ) {
7552         /* black pawn promotion */
7553         board[0][toX] = CharToPiece(ToLower(promoChar));
7554         if (board[0][toX] == EmptySquare) {
7555             board[0][toX] = BlackQueen;
7556         }
7557         if(gameInfo.variant==VariantBughouse ||
7558            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7559             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7560         board[fromY][fromX] = EmptySquare;
7561     } else if ((fromY == 3)
7562                && (toX != fromX)
7563                && gameInfo.variant != VariantXiangqi
7564                && gameInfo.variant != VariantBerolina
7565                && (board[fromY][fromX] == BlackPawn)
7566                && (board[toY][toX] == EmptySquare)) {
7567         board[fromY][fromX] = EmptySquare;
7568         board[toY][toX] = BlackPawn;
7569         captured = board[toY + 1][toX];
7570         board[toY + 1][toX] = EmptySquare;
7571     } else if ((fromY == 3)
7572                && (toX == fromX)
7573                && gameInfo.variant == VariantBerolina
7574                && (board[fromY][fromX] == BlackPawn)
7575                && (board[toY][toX] == EmptySquare)) {
7576         board[fromY][fromX] = EmptySquare;
7577         board[toY][toX] = BlackPawn;
7578         if(oldEP & EP_BEROLIN_A) {
7579                 captured = board[fromY][fromX-1];
7580                 board[fromY][fromX-1] = EmptySquare;
7581         }else{  captured = board[fromY][fromX+1];
7582                 board[fromY][fromX+1] = EmptySquare;
7583         }
7584     } else {
7585         board[toY][toX] = board[fromY][fromX];
7586         board[fromY][fromX] = EmptySquare;
7587     }
7588
7589     /* [HGM] now we promote for Shogi, if needed */
7590     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7591         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7592   }
7593
7594     if (gameInfo.holdingsWidth != 0) {
7595
7596       /* !!A lot more code needs to be written to support holdings  */
7597       /* [HGM] OK, so I have written it. Holdings are stored in the */
7598       /* penultimate board files, so they are automaticlly stored   */
7599       /* in the game history.                                       */
7600       if (fromY == DROP_RANK) {
7601         /* Delete from holdings, by decreasing count */
7602         /* and erasing image if necessary            */
7603         p = (int) fromX;
7604         if(p < (int) BlackPawn) { /* white drop */
7605              p -= (int)WhitePawn;
7606                  p = PieceToNumber((ChessSquare)p);
7607              if(p >= gameInfo.holdingsSize) p = 0;
7608              if(--board[p][BOARD_WIDTH-2] <= 0)
7609                   board[p][BOARD_WIDTH-1] = EmptySquare;
7610              if((int)board[p][BOARD_WIDTH-2] < 0)
7611                         board[p][BOARD_WIDTH-2] = 0;
7612         } else {                  /* black drop */
7613              p -= (int)BlackPawn;
7614                  p = PieceToNumber((ChessSquare)p);
7615              if(p >= gameInfo.holdingsSize) p = 0;
7616              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7617                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7618              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7619                         board[BOARD_HEIGHT-1-p][1] = 0;
7620         }
7621       }
7622       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7623           && gameInfo.variant != VariantBughouse        ) {
7624         /* [HGM] holdings: Add to holdings, if holdings exist */
7625         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7626                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7627                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7628         }
7629         p = (int) captured;
7630         if (p >= (int) BlackPawn) {
7631           p -= (int)BlackPawn;
7632           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7633                   /* in Shogi restore piece to its original  first */
7634                   captured = (ChessSquare) (DEMOTED captured);
7635                   p = DEMOTED p;
7636           }
7637           p = PieceToNumber((ChessSquare)p);
7638           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7639           board[p][BOARD_WIDTH-2]++;
7640           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7641         } else {
7642           p -= (int)WhitePawn;
7643           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7644                   captured = (ChessSquare) (DEMOTED captured);
7645                   p = DEMOTED p;
7646           }
7647           p = PieceToNumber((ChessSquare)p);
7648           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7649           board[BOARD_HEIGHT-1-p][1]++;
7650           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7651         }
7652       }
7653     } else if (gameInfo.variant == VariantAtomic) {
7654       if (captured != EmptySquare) {
7655         int y, x;
7656         for (y = toY-1; y <= toY+1; y++) {
7657           for (x = toX-1; x <= toX+1; x++) {
7658             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7659                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7660               board[y][x] = EmptySquare;
7661             }
7662           }
7663         }
7664         board[toY][toX] = EmptySquare;
7665       }
7666     }
7667     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7668         /* [HGM] Shogi promotions */
7669         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7670     }
7671
7672     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7673                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7674         // [HGM] superchess: take promotion piece out of holdings
7675         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7676         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7677             if(!--board[k][BOARD_WIDTH-2])
7678                 board[k][BOARD_WIDTH-1] = EmptySquare;
7679         } else {
7680             if(!--board[BOARD_HEIGHT-1-k][1])
7681                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7682         }
7683     }
7684
7685 }
7686
7687 /* Updates forwardMostMove */
7688 void
7689 MakeMove(fromX, fromY, toX, toY, promoChar)
7690      int fromX, fromY, toX, toY;
7691      int promoChar;
7692 {
7693 //    forwardMostMove++; // [HGM] bare: moved downstream
7694
7695     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7696         int timeLeft; static int lastLoadFlag=0; int king, piece;
7697         piece = boards[forwardMostMove][fromY][fromX];
7698         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7699         if(gameInfo.variant == VariantKnightmate)
7700             king += (int) WhiteUnicorn - (int) WhiteKing;
7701         if(forwardMostMove == 0) {
7702             if(blackPlaysFirst)
7703                 fprintf(serverMoves, "%s;", second.tidy);
7704             fprintf(serverMoves, "%s;", first.tidy);
7705             if(!blackPlaysFirst)
7706                 fprintf(serverMoves, "%s;", second.tidy);
7707         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7708         lastLoadFlag = loadFlag;
7709         // print base move
7710         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7711         // print castling suffix
7712         if( toY == fromY && piece == king ) {
7713             if(toX-fromX > 1)
7714                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7715             if(fromX-toX >1)
7716                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7717         }
7718         // e.p. suffix
7719         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7720              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7721              boards[forwardMostMove][toY][toX] == EmptySquare
7722              && fromX != toX )
7723                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7724         // promotion suffix
7725         if(promoChar != NULLCHAR)
7726                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7727         if(!loadFlag) {
7728             fprintf(serverMoves, "/%d/%d",
7729                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7730             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7731             else                      timeLeft = blackTimeRemaining/1000;
7732             fprintf(serverMoves, "/%d", timeLeft);
7733         }
7734         fflush(serverMoves);
7735     }
7736
7737     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7738       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7739                         0, 1);
7740       return;
7741     }
7742     if (commentList[forwardMostMove+1] != NULL) {
7743         free(commentList[forwardMostMove+1]);
7744         commentList[forwardMostMove+1] = NULL;
7745     }
7746     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7747     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7748     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7749     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7750     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7751     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7752     gameInfo.result = GameUnfinished;
7753     if (gameInfo.resultDetails != NULL) {
7754         free(gameInfo.resultDetails);
7755         gameInfo.resultDetails = NULL;
7756     }
7757     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7758                               moveList[forwardMostMove - 1]);
7759     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7760                              PosFlags(forwardMostMove - 1),
7761                              fromY, fromX, toY, toX, promoChar,
7762                              parseList[forwardMostMove - 1]);
7763     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7764       case MT_NONE:
7765       case MT_STALEMATE:
7766       default:
7767         break;
7768       case MT_CHECK:
7769         if(gameInfo.variant != VariantShogi)
7770             strcat(parseList[forwardMostMove - 1], "+");
7771         break;
7772       case MT_CHECKMATE:
7773       case MT_STAINMATE:
7774         strcat(parseList[forwardMostMove - 1], "#");
7775         break;
7776     }
7777     if (appData.debugMode) {
7778         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7779     }
7780
7781 }
7782
7783 /* Updates currentMove if not pausing */
7784 void
7785 ShowMove(fromX, fromY, toX, toY)
7786 {
7787     int instant = (gameMode == PlayFromGameFile) ?
7788         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7789
7790     if(appData.noGUI) return;
7791
7792     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7793       {
7794         if (!instant)
7795           {
7796             if (forwardMostMove == currentMove + 1)
7797               {
7798 //TODO
7799 //              AnimateMove(boards[forwardMostMove - 1],
7800 //                          fromX, fromY, toX, toY);
7801               }
7802             if (appData.highlightLastMove)
7803               {
7804                 SetHighlights(fromX, fromY, toX, toY);
7805               }
7806           }
7807         currentMove = forwardMostMove;
7808     }
7809
7810     if (instant) return;
7811
7812     DisplayMove(currentMove - 1);
7813     DrawPosition(FALSE, boards[currentMove]);
7814     DisplayBothClocks();
7815     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7816
7817     return;
7818 }
7819
7820 void SendEgtPath(ChessProgramState *cps)
7821 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7822         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7823
7824         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7825
7826         while(*p) {
7827             char c, *q = name+1, *r, *s;
7828
7829             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7830             while(*p && *p != ',') *q++ = *p++;
7831             *q++ = ':'; *q = 0;
7832             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7833                 strcmp(name, ",nalimov:") == 0 ) {
7834                 // take nalimov path from the menu-changeable option first, if it is defined
7835                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7836                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7837             } else
7838             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7839                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7840                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7841                 s = r = StrStr(s, ":") + 1; // beginning of path info
7842                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7843                 c = *r; *r = 0;             // temporarily null-terminate path info
7844                     *--q = 0;               // strip of trailig ':' from name
7845                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7846                 *r = c;
7847                 SendToProgram(buf,cps);     // send egtbpath command for this format
7848             }
7849             if(*p == ',') p++; // read away comma to position for next format name
7850         }
7851 }
7852
7853 void
7854 InitChessProgram(cps, setup)
7855      ChessProgramState *cps;
7856      int setup; /* [HGM] needed to setup FRC opening position */
7857 {
7858     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7859     if (appData.noChessProgram) return;
7860     hintRequested = FALSE;
7861     bookRequested = FALSE;
7862
7863     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7864     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7865     if(cps->memSize) { /* [HGM] memory */
7866         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7867         SendToProgram(buf, cps);
7868     }
7869     SendEgtPath(cps); /* [HGM] EGT */
7870     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7871         sprintf(buf, "cores %d\n", appData.smpCores);
7872         SendToProgram(buf, cps);
7873     }
7874
7875     SendToProgram(cps->initString, cps);
7876     if (gameInfo.variant != VariantNormal &&
7877         gameInfo.variant != VariantLoadable
7878         /* [HGM] also send variant if board size non-standard */
7879         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7880                                             ) {
7881       char *v = VariantName(gameInfo.variant);
7882       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7883         /* [HGM] in protocol 1 we have to assume all variants valid */
7884         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7885         DisplayFatalError(buf, 0, 1);
7886         return;
7887       }
7888
7889       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7890       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7891       if( gameInfo.variant == VariantXiangqi )
7892            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7893       if( gameInfo.variant == VariantShogi )
7894            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7895       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7896            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7897       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7898                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7899            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7900       if( gameInfo.variant == VariantCourier )
7901            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7902       if( gameInfo.variant == VariantSuper )
7903            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7904       if( gameInfo.variant == VariantGreat )
7905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7906
7907       if(overruled) {
7908            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7909                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7910            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7911            if(StrStr(cps->variants, b) == NULL) {
7912                // specific sized variant not known, check if general sizing allowed
7913                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7914                    if(StrStr(cps->variants, "boardsize") == NULL) {
7915                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7916                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7917                        DisplayFatalError(buf, 0, 1);
7918                        return;
7919                    }
7920                    /* [HGM] here we really should compare with the maximum supported board size */
7921                }
7922            }
7923       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7924       sprintf(buf, "variant %s\n", b);
7925       SendToProgram(buf, cps);
7926     }
7927     currentlyInitializedVariant = gameInfo.variant;
7928
7929     /* [HGM] send opening position in FRC to first engine */
7930     if(setup) {
7931           SendToProgram("force\n", cps);
7932           SendBoard(cps, 0);
7933           /* engine is now in force mode! Set flag to wake it up after first move. */
7934           setboardSpoiledMachineBlack = 1;
7935     }
7936
7937     if (cps->sendICS) {
7938       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7939       SendToProgram(buf, cps);
7940     }
7941     cps->maybeThinking = FALSE;
7942     cps->offeredDraw = 0;
7943     if (!appData.icsActive) {
7944         SendTimeControl(cps, movesPerSession, timeControl,
7945                         timeIncrement, appData.searchDepth,
7946                         searchTime);
7947     }
7948     if (appData.showThinking
7949         // [HGM] thinking: four options require thinking output to be sent
7950         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7951                                 ) {
7952         SendToProgram("post\n", cps);
7953     }
7954     SendToProgram("hard\n", cps);
7955     if (!appData.ponderNextMove) {
7956         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7957            it without being sure what state we are in first.  "hard"
7958            is not a toggle, so that one is OK.
7959          */
7960         SendToProgram("easy\n", cps);
7961     }
7962     if (cps->usePing) {
7963       sprintf(buf, "ping %d\n", ++cps->lastPing);
7964       SendToProgram(buf, cps);
7965     }
7966     cps->initDone = TRUE;
7967 }
7968
7969
7970 void
7971 StartChessProgram(cps)
7972      ChessProgramState *cps;
7973 {
7974     char buf[MSG_SIZ];
7975     int err;
7976
7977     if (appData.noChessProgram) return;
7978     cps->initDone = FALSE;
7979
7980     if (strcmp(cps->host, "localhost") == 0) {
7981         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7982     } else if (*appData.remoteShell == NULLCHAR) {
7983         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7984     } else {
7985         if (*appData.remoteUser == NULLCHAR) {
7986           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7987                     cps->program);
7988         } else {
7989           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7990                     cps->host, appData.remoteUser, cps->program);
7991         }
7992         err = StartChildProcess(buf, "", &cps->pr);
7993     }
7994
7995     if (err != 0) {
7996         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7997         DisplayFatalError(buf, err, 1);
7998         cps->pr = NoProc;
7999         cps->isr = NULL;
8000         return;
8001     }
8002
8003     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8004     if (cps->protocolVersion > 1) {
8005       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8006       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8007       cps->comboCnt = 0;  //                and values of combo boxes
8008       SendToProgram(buf, cps);
8009     } else {
8010       SendToProgram("xboard\n", cps);
8011     }
8012 }
8013
8014
8015 void
8016 TwoMachinesEventIfReady P((void))
8017 {
8018   if (first.lastPing != first.lastPong) {
8019     DisplayMessage("", _("Waiting for first chess program"));
8020     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8021     return;
8022   }
8023   if (second.lastPing != second.lastPong) {
8024     DisplayMessage("", _("Waiting for second chess program"));
8025     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8026     return;
8027   }
8028   ThawUI();
8029   TwoMachinesEvent();
8030 }
8031
8032 void
8033 NextMatchGame P((void))
8034 {
8035     int index; /* [HGM] autoinc: step load index during match */
8036     Reset(FALSE, TRUE);
8037     if (*appData.loadGameFile != NULLCHAR) {
8038         index = appData.loadGameIndex;
8039         if(index < 0) { // [HGM] autoinc
8040             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8041             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8042         }
8043         LoadGameFromFile(appData.loadGameFile,
8044                          index,
8045                          appData.loadGameFile, FALSE);
8046     } else if (*appData.loadPositionFile != NULLCHAR) {
8047         index = appData.loadPositionIndex;
8048         if(index < 0) { // [HGM] autoinc
8049             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8050             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8051         }
8052         LoadPositionFromFile(appData.loadPositionFile,
8053                              index,
8054                              appData.loadPositionFile);
8055     }
8056     TwoMachinesEventIfReady();
8057 }
8058
8059 void UserAdjudicationEvent( int result )
8060 {
8061     ChessMove gameResult = GameIsDrawn;
8062
8063     if( result > 0 ) {
8064         gameResult = WhiteWins;
8065     }
8066     else if( result < 0 ) {
8067         gameResult = BlackWins;
8068     }
8069
8070     if( gameMode == TwoMachinesPlay ) {
8071         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8072     }
8073 }
8074
8075
8076 // [HGM] save: calculate checksum of game to make games easily identifiable
8077 int StringCheckSum(char *s)
8078 {
8079         int i = 0;
8080         if(s==NULL) return 0;
8081         while(*s) i = i*259 + *s++;
8082         return i;
8083 }
8084
8085 int GameCheckSum()
8086 {
8087         int i, sum=0;
8088         for(i=backwardMostMove; i<forwardMostMove; i++) {
8089                 sum += pvInfoList[i].depth;
8090                 sum += StringCheckSum(parseList[i]);
8091                 sum += StringCheckSum(commentList[i]);
8092                 sum *= 261;
8093         }
8094         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8095         return sum + StringCheckSum(commentList[i]);
8096 } // end of save patch
8097
8098 void
8099 GameEnds(result, resultDetails, whosays)
8100      ChessMove result;
8101      char *resultDetails;
8102      int whosays;
8103 {
8104     GameMode nextGameMode;
8105     int isIcsGame;
8106     char buf[MSG_SIZ];
8107
8108     if(endingGame) return; /* [HGM] crash: forbid recursion */
8109     endingGame = 1;
8110
8111     if (appData.debugMode) {
8112       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8113               result, resultDetails ? resultDetails : "(null)", whosays);
8114     }
8115
8116     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8117         /* If we are playing on ICS, the server decides when the
8118            game is over, but the engine can offer to draw, claim
8119            a draw, or resign.
8120          */
8121 #if ZIPPY
8122         if (appData.zippyPlay && first.initDone) {
8123             if (result == GameIsDrawn) {
8124                 /* In case draw still needs to be claimed */
8125                 SendToICS(ics_prefix);
8126                 SendToICS("draw\n");
8127             } else if (StrCaseStr(resultDetails, "resign")) {
8128                 SendToICS(ics_prefix);
8129                 SendToICS("resign\n");
8130             }
8131         }
8132 #endif
8133         endingGame = 0; /* [HGM] crash */
8134         return;
8135     }
8136
8137     /* If we're loading the game from a file, stop */
8138     if (whosays == GE_FILE) {
8139       (void) StopLoadGameTimer();
8140       gameFileFP = NULL;
8141     }
8142
8143     /* Cancel draw offers */
8144     first.offeredDraw = second.offeredDraw = 0;
8145
8146     /* If this is an ICS game, only ICS can really say it's done;
8147        if not, anyone can. */
8148     isIcsGame = (gameMode == IcsPlayingWhite ||
8149                  gameMode == IcsPlayingBlack ||
8150                  gameMode == IcsObserving    ||
8151                  gameMode == IcsExamining);
8152
8153     if (!isIcsGame || whosays == GE_ICS) {
8154         /* OK -- not an ICS game, or ICS said it was done */
8155         StopClocks();
8156         if (!isIcsGame && !appData.noChessProgram)
8157           SetUserThinkingEnables();
8158
8159         /* [HGM] if a machine claims the game end we verify this claim */
8160         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8161             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8162                 char claimer;
8163                 ChessMove trueResult = (ChessMove) -1;
8164
8165                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8166                                             first.twoMachinesColor[0] :
8167                                             second.twoMachinesColor[0] ;
8168
8169                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8170                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8171                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8172                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8173                 } else
8174                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8175                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8176                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8177                 } else
8178                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8179                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8180                 }
8181
8182                 // now verify win claims, but not in drop games, as we don't understand those yet
8183                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8184                                                  || gameInfo.variant == VariantGreat) &&
8185                     (result == WhiteWins && claimer == 'w' ||
8186                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8187                       if (appData.debugMode) {
8188                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8189                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8190                       }
8191                       if(result != trueResult) {
8192                               sprintf(buf, "False win claim: '%s'", resultDetails);
8193                               result = claimer == 'w' ? BlackWins : WhiteWins;
8194                               resultDetails = buf;
8195                       }
8196                 } else
8197                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8198                     && (forwardMostMove <= backwardMostMove ||
8199                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8200                         (claimer=='b')==(forwardMostMove&1))
8201                                                                                   ) {
8202                       /* [HGM] verify: draws that were not flagged are false claims */
8203                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8204                       result = claimer == 'w' ? BlackWins : WhiteWins;
8205                       resultDetails = buf;
8206                 }
8207                 /* (Claiming a loss is accepted no questions asked!) */
8208             }
8209
8210             /* [HGM] bare: don't allow bare King to win */
8211             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8212                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8213                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8214                && result != GameIsDrawn)
8215             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8216                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8217                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8218                         if(p >= 0 && p <= (int)WhiteKing) k++;
8219                 }
8220                 if (appData.debugMode) {
8221                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8222                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8223                 }
8224                 if(k <= 1) {
8225                         result = GameIsDrawn;
8226                         sprintf(buf, "%s but bare king", resultDetails);
8227                         resultDetails = buf;
8228                 }
8229             }
8230         }
8231
8232         if(serverMoves != NULL && !loadFlag) { char c = '=';
8233             if(result==WhiteWins) c = '+';
8234             if(result==BlackWins) c = '-';
8235             if(resultDetails != NULL)
8236                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8237         }
8238         if (resultDetails != NULL) {
8239             gameInfo.result = result;
8240             gameInfo.resultDetails = StrSave(resultDetails);
8241
8242             /* display last move only if game was not loaded from file */
8243             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8244                 DisplayMove(currentMove - 1);
8245
8246             if (forwardMostMove != 0) {
8247                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8248                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8249                                                                 ) {
8250                     if (*appData.saveGameFile != NULLCHAR) {
8251                         SaveGameToFile(appData.saveGameFile, TRUE);
8252                     } else if (appData.autoSaveGames) {
8253                         AutoSaveGame();
8254                     }
8255                     if (*appData.savePositionFile != NULLCHAR) {
8256                         SavePositionToFile(appData.savePositionFile);
8257                     }
8258                 }
8259             }
8260
8261             /* Tell program how game ended in case it is learning */
8262             /* [HGM] Moved this to after saving the PGN, just in case */
8263             /* engine died and we got here through time loss. In that */
8264             /* case we will get a fatal error writing the pipe, which */
8265             /* would otherwise lose us the PGN.                       */
8266             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8267             /* output during GameEnds should never be fatal anymore   */
8268             if (gameMode == MachinePlaysWhite ||
8269                 gameMode == MachinePlaysBlack ||
8270                 gameMode == TwoMachinesPlay ||
8271                 gameMode == IcsPlayingWhite ||
8272                 gameMode == IcsPlayingBlack ||
8273                 gameMode == BeginningOfGame) {
8274                 char buf[MSG_SIZ];
8275                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8276                         resultDetails);
8277                 if (first.pr != NoProc) {
8278                     SendToProgram(buf, &first);
8279                 }
8280                 if (second.pr != NoProc &&
8281                     gameMode == TwoMachinesPlay) {
8282                     SendToProgram(buf, &second);
8283                 }
8284             }
8285         }
8286
8287         if (appData.icsActive) {
8288             if (appData.quietPlay &&
8289                 (gameMode == IcsPlayingWhite ||
8290                  gameMode == IcsPlayingBlack)) {
8291                 SendToICS(ics_prefix);
8292                 SendToICS("set shout 1\n");
8293             }
8294             nextGameMode = IcsIdle;
8295             ics_user_moved = FALSE;
8296             /* clean up premove.  It's ugly when the game has ended and the
8297              * premove highlights are still on the board.
8298              */
8299             if (gotPremove) {
8300               gotPremove = FALSE;
8301               ClearPremoveHighlights();
8302               DrawPosition(FALSE, boards[currentMove]);
8303             }
8304             if (whosays == GE_ICS) {
8305                 switch (result) {
8306                 case WhiteWins:
8307                     if (gameMode == IcsPlayingWhite)
8308                         PlayIcsWinSound();
8309                     else if(gameMode == IcsPlayingBlack)
8310                         PlayIcsLossSound();
8311                     break;
8312                 case BlackWins:
8313                     if (gameMode == IcsPlayingBlack)
8314                         PlayIcsWinSound();
8315                     else if(gameMode == IcsPlayingWhite)
8316                         PlayIcsLossSound();
8317                     break;
8318                 case GameIsDrawn:
8319                     PlayIcsDrawSound();
8320                     break;
8321                 default:
8322                     PlayIcsUnfinishedSound();
8323                 }
8324             }
8325         } else if (gameMode == EditGame ||
8326                    gameMode == PlayFromGameFile ||
8327                    gameMode == AnalyzeMode ||
8328                    gameMode == AnalyzeFile) {
8329             nextGameMode = gameMode;
8330         } else {
8331             nextGameMode = EndOfGame;
8332         }
8333         pausing = FALSE;
8334         ModeHighlight();
8335     } else {
8336         nextGameMode = gameMode;
8337     }
8338
8339     if (appData.noChessProgram) {
8340         gameMode = nextGameMode;
8341         ModeHighlight();
8342         endingGame = 0; /* [HGM] crash */
8343         return;
8344     }
8345
8346     if (first.reuse) {
8347         /* Put first chess program into idle state */
8348         if (first.pr != NoProc &&
8349             (gameMode == MachinePlaysWhite ||
8350              gameMode == MachinePlaysBlack ||
8351              gameMode == TwoMachinesPlay ||
8352              gameMode == IcsPlayingWhite ||
8353              gameMode == IcsPlayingBlack ||
8354              gameMode == BeginningOfGame)) {
8355             SendToProgram("force\n", &first);
8356             if (first.usePing) {
8357               char buf[MSG_SIZ];
8358               sprintf(buf, "ping %d\n", ++first.lastPing);
8359               SendToProgram(buf, &first);
8360             }
8361         }
8362     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8363         /* Kill off first chess program */
8364         if (first.isr != NULL)
8365           RemoveInputSource(first.isr);
8366         first.isr = NULL;
8367
8368         if (first.pr != NoProc) {
8369             ExitAnalyzeMode();
8370             DoSleep( appData.delayBeforeQuit );
8371             SendToProgram("quit\n", &first);
8372             DoSleep( appData.delayAfterQuit );
8373             DestroyChildProcess(first.pr, first.useSigterm);
8374         }
8375         first.pr = NoProc;
8376     }
8377     if (second.reuse) {
8378         /* Put second chess program into idle state */
8379         if (second.pr != NoProc &&
8380             gameMode == TwoMachinesPlay) {
8381             SendToProgram("force\n", &second);
8382             if (second.usePing) {
8383               char buf[MSG_SIZ];
8384               sprintf(buf, "ping %d\n", ++second.lastPing);
8385               SendToProgram(buf, &second);
8386             }
8387         }
8388     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8389         /* Kill off second chess program */
8390         if (second.isr != NULL)
8391           RemoveInputSource(second.isr);
8392         second.isr = NULL;
8393
8394         if (second.pr != NoProc) {
8395             DoSleep( appData.delayBeforeQuit );
8396             SendToProgram("quit\n", &second);
8397             DoSleep( appData.delayAfterQuit );
8398             DestroyChildProcess(second.pr, second.useSigterm);
8399         }
8400         second.pr = NoProc;
8401     }
8402
8403     if (matchMode && gameMode == TwoMachinesPlay) {
8404         switch (result) {
8405         case WhiteWins:
8406           if (first.twoMachinesColor[0] == 'w') {
8407             first.matchWins++;
8408           } else {
8409             second.matchWins++;
8410           }
8411           break;
8412         case BlackWins:
8413           if (first.twoMachinesColor[0] == 'b') {
8414             first.matchWins++;
8415           } else {
8416             second.matchWins++;
8417           }
8418           break;
8419         default:
8420           break;
8421         }
8422         if (matchGame < appData.matchGames) {
8423             char *tmp;
8424             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8425                 tmp = first.twoMachinesColor;
8426                 first.twoMachinesColor = second.twoMachinesColor;
8427                 second.twoMachinesColor = tmp;
8428             }
8429             gameMode = nextGameMode;
8430             matchGame++;
8431             if(appData.matchPause>10000 || appData.matchPause<10)
8432                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8433             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8434             endingGame = 0; /* [HGM] crash */
8435             return;
8436         } else {
8437             char buf[MSG_SIZ];
8438             gameMode = nextGameMode;
8439             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8440                     first.tidy, second.tidy,
8441                     first.matchWins, second.matchWins,
8442                     appData.matchGames - (first.matchWins + second.matchWins));
8443             DisplayFatalError(buf, 0, 0);
8444         }
8445     }
8446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8447         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8448       ExitAnalyzeMode();
8449     gameMode = nextGameMode;
8450     ModeHighlight();
8451     endingGame = 0;  /* [HGM] crash */
8452 }
8453
8454 /* Assumes program was just initialized (initString sent).
8455    Leaves program in force mode. */
8456 void
8457 FeedMovesToProgram(cps, upto)
8458      ChessProgramState *cps;
8459      int upto;
8460 {
8461     int i;
8462
8463     if (appData.debugMode)
8464       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8465               startedFromSetupPosition ? "position and " : "",
8466               backwardMostMove, upto, cps->which);
8467     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8468         // [HGM] variantswitch: make engine aware of new variant
8469         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8470                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8471         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8472         SendToProgram(buf, cps);
8473         currentlyInitializedVariant = gameInfo.variant;
8474     }
8475     SendToProgram("force\n", cps);
8476     if (startedFromSetupPosition) {
8477         SendBoard(cps, backwardMostMove);
8478     if (appData.debugMode) {
8479         fprintf(debugFP, "feedMoves\n");
8480     }
8481     }
8482     for (i = backwardMostMove; i < upto; i++) {
8483         SendMoveToProgram(i, cps);
8484     }
8485 }
8486
8487
8488 void
8489 ResurrectChessProgram()
8490 {
8491      /* The chess program may have exited.
8492         If so, restart it and feed it all the moves made so far. */
8493
8494     if (appData.noChessProgram || first.pr != NoProc) return;
8495
8496     StartChessProgram(&first);
8497     InitChessProgram(&first, FALSE);
8498     FeedMovesToProgram(&first, currentMove);
8499
8500     if (!first.sendTime) {
8501         /* can't tell gnuchess what its clock should read,
8502            so we bow to its notion. */
8503         ResetClocks();
8504         timeRemaining[0][currentMove] = whiteTimeRemaining;
8505         timeRemaining[1][currentMove] = blackTimeRemaining;
8506     }
8507
8508     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8509                 appData.icsEngineAnalyze) && first.analysisSupport) {
8510       SendToProgram("analyze\n", &first);
8511       first.analyzing = TRUE;
8512     }
8513 }
8514
8515 /*
8516  * Button procedures
8517  */
8518 void
8519 Reset(redraw, init)
8520      int redraw, init;
8521 {
8522     int i;
8523
8524     if (appData.debugMode) {
8525         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8526                 redraw, init, gameMode);
8527     }
8528     CleanupTail(); // [HGM] vari: delete any stored variations
8529     pausing = pauseExamInvalid = FALSE;
8530     startedFromSetupPosition = blackPlaysFirst = FALSE;
8531     firstMove = TRUE;
8532     whiteFlag = blackFlag = FALSE;
8533     userOfferedDraw = FALSE;
8534     hintRequested = bookRequested = FALSE;
8535     first.maybeThinking = FALSE;
8536     second.maybeThinking = FALSE;
8537     first.bookSuspend = FALSE; // [HGM] book
8538     second.bookSuspend = FALSE;
8539     thinkOutput[0] = NULLCHAR;
8540     lastHint[0] = NULLCHAR;
8541     ClearGameInfo(&gameInfo);
8542     gameInfo.variant = StringToVariant(appData.variant);
8543     ics_user_moved = ics_clock_paused = FALSE;
8544     ics_getting_history = H_FALSE;
8545     ics_gamenum = -1;
8546     white_holding[0] = black_holding[0] = NULLCHAR;
8547     ClearProgramStats();
8548     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8549
8550     ResetFrontEnd();
8551     ClearHighlights();
8552     flipView = appData.flipView;
8553     ClearPremoveHighlights();
8554     gotPremove = FALSE;
8555     alarmSounded = FALSE;
8556
8557     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8558     if(appData.serverMovesName != NULL) {
8559         /* [HGM] prepare to make moves file for broadcasting */
8560         clock_t t = clock();
8561         if(serverMoves != NULL) fclose(serverMoves);
8562         serverMoves = fopen(appData.serverMovesName, "r");
8563         if(serverMoves != NULL) {
8564             fclose(serverMoves);
8565             /* delay 15 sec before overwriting, so all clients can see end */
8566             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8567         }
8568         serverMoves = fopen(appData.serverMovesName, "w");
8569     }
8570
8571     ExitAnalyzeMode();
8572     gameMode = BeginningOfGame;
8573     ModeHighlight();
8574
8575     if(appData.icsActive) gameInfo.variant = VariantNormal;
8576     currentMove = forwardMostMove = backwardMostMove = 0;
8577     InitPosition(redraw);
8578     for (i = 0; i < MAX_MOVES; i++) {
8579         if (commentList[i] != NULL) {
8580             free(commentList[i]);
8581             commentList[i] = NULL;
8582         }
8583     }
8584
8585     ResetClocks();
8586     timeRemaining[0][0] = whiteTimeRemaining;
8587     timeRemaining[1][0] = blackTimeRemaining;
8588     if (first.pr == NULL) {
8589         StartChessProgram(&first);
8590     }
8591     if (init) {
8592             InitChessProgram(&first, startedFromSetupPosition);
8593     }
8594
8595     DisplayTitle("");
8596     DisplayMessage("", "");
8597     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8598     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8599     return;
8600 }
8601
8602 void
8603 AutoPlayGameLoop()
8604 {
8605     for (;;) {
8606         if (!AutoPlayOneMove())
8607           return;
8608         if (matchMode || appData.timeDelay == 0)
8609           continue;
8610         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8611           return;
8612         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8613         break;
8614     }
8615 }
8616
8617
8618 int
8619 AutoPlayOneMove()
8620 {
8621     int fromX, fromY, toX, toY;
8622
8623     if (appData.debugMode) {
8624       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8625     }
8626
8627     if (gameMode != PlayFromGameFile)
8628       return FALSE;
8629
8630     if (currentMove >= forwardMostMove) {
8631       gameMode = EditGame;
8632       ModeHighlight();
8633
8634       /* [AS] Clear current move marker at the end of a game */
8635       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8636
8637       return FALSE;
8638     }
8639
8640     toX = moveList[currentMove][2] - AAA;
8641     toY = moveList[currentMove][3] - ONE;
8642
8643     if (moveList[currentMove][1] == '@') {
8644         if (appData.highlightLastMove) {
8645             SetHighlights(-1, -1, toX, toY);
8646         }
8647     } else {
8648         fromX = moveList[currentMove][0] - AAA;
8649         fromY = moveList[currentMove][1] - ONE;
8650
8651         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8652
8653         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8654
8655         if (appData.highlightLastMove) {
8656             SetHighlights(fromX, fromY, toX, toY);
8657         }
8658     }
8659     DisplayMove(currentMove);
8660     SendMoveToProgram(currentMove++, &first);
8661     DisplayBothClocks();
8662     DrawPosition(FALSE, boards[currentMove]);
8663     // [HGM] PV info: always display, routine tests if empty
8664     DisplayComment(currentMove - 1, commentList[currentMove]);
8665     return TRUE;
8666 }
8667
8668
8669 int
8670 LoadGameOneMove(readAhead)
8671      ChessMove readAhead;
8672 {
8673     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8674     char promoChar = NULLCHAR;
8675     ChessMove moveType;
8676     char move[MSG_SIZ];
8677     char *p, *q;
8678
8679     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8680         gameMode != AnalyzeMode && gameMode != Training) {
8681         gameFileFP = NULL;
8682         return FALSE;
8683     }
8684
8685     yyboardindex = forwardMostMove;
8686     if (readAhead != (ChessMove)0) {
8687       moveType = readAhead;
8688     } else {
8689       if (gameFileFP == NULL)
8690           return FALSE;
8691       moveType = (ChessMove) yylex();
8692     }
8693
8694     done = FALSE;
8695     switch (moveType) {
8696       case Comment:
8697         if (appData.debugMode)
8698           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8699         p = yy_text;
8700
8701         /* append the comment but don't display it */
8702         AppendComment(currentMove, p, FALSE);
8703         return TRUE;
8704
8705       case WhiteCapturesEnPassant:
8706       case BlackCapturesEnPassant:
8707       case WhitePromotionChancellor:
8708       case BlackPromotionChancellor:
8709       case WhitePromotionArchbishop:
8710       case BlackPromotionArchbishop:
8711       case WhitePromotionCentaur:
8712       case BlackPromotionCentaur:
8713       case WhitePromotionQueen:
8714       case BlackPromotionQueen:
8715       case WhitePromotionRook:
8716       case BlackPromotionRook:
8717       case WhitePromotionBishop:
8718       case BlackPromotionBishop:
8719       case WhitePromotionKnight:
8720       case BlackPromotionKnight:
8721       case WhitePromotionKing:
8722       case BlackPromotionKing:
8723       case NormalMove:
8724       case WhiteKingSideCastle:
8725       case WhiteQueenSideCastle:
8726       case BlackKingSideCastle:
8727       case BlackQueenSideCastle:
8728       case WhiteKingSideCastleWild:
8729       case WhiteQueenSideCastleWild:
8730       case BlackKingSideCastleWild:
8731       case BlackQueenSideCastleWild:
8732       /* PUSH Fabien */
8733       case WhiteHSideCastleFR:
8734       case WhiteASideCastleFR:
8735       case BlackHSideCastleFR:
8736       case BlackASideCastleFR:
8737       /* POP Fabien */
8738         if (appData.debugMode)
8739           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8740         fromX = currentMoveString[0] - AAA;
8741         fromY = currentMoveString[1] - ONE;
8742         toX = currentMoveString[2] - AAA;
8743         toY = currentMoveString[3] - ONE;
8744         promoChar = currentMoveString[4];
8745         break;
8746
8747       case WhiteDrop:
8748       case BlackDrop:
8749         if (appData.debugMode)
8750           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8751         fromX = moveType == WhiteDrop ?
8752           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8753         (int) CharToPiece(ToLower(currentMoveString[0]));
8754         fromY = DROP_RANK;
8755         toX = currentMoveString[2] - AAA;
8756         toY = currentMoveString[3] - ONE;
8757         break;
8758
8759       case WhiteWins:
8760       case BlackWins:
8761       case GameIsDrawn:
8762       case GameUnfinished:
8763         if (appData.debugMode)
8764           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8765         p = strchr(yy_text, '{');
8766         if (p == NULL) p = strchr(yy_text, '(');
8767         if (p == NULL) {
8768             p = yy_text;
8769             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8770         } else {
8771             q = strchr(p, *p == '{' ? '}' : ')');
8772             if (q != NULL) *q = NULLCHAR;
8773             p++;
8774         }
8775         GameEnds(moveType, p, GE_FILE);
8776         done = TRUE;
8777         if (cmailMsgLoaded) {
8778             ClearHighlights();
8779             flipView = WhiteOnMove(currentMove);
8780             if (moveType == GameUnfinished) flipView = !flipView;
8781             if (appData.debugMode)
8782               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8783         }
8784         break;
8785
8786       case (ChessMove) 0:       /* end of file */
8787         if (appData.debugMode)
8788           fprintf(debugFP, "Parser hit end of file\n");
8789         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8790           case MT_NONE:
8791           case MT_CHECK:
8792             break;
8793           case MT_CHECKMATE:
8794           case MT_STAINMATE:
8795             if (WhiteOnMove(currentMove)) {
8796                 GameEnds(BlackWins, "Black mates", GE_FILE);
8797             } else {
8798                 GameEnds(WhiteWins, "White mates", GE_FILE);
8799             }
8800             break;
8801           case MT_STALEMATE:
8802             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8803             break;
8804         }
8805         done = TRUE;
8806         break;
8807
8808       case MoveNumberOne:
8809         if (lastLoadGameStart == GNUChessGame) {
8810             /* GNUChessGames have numbers, but they aren't move numbers */
8811             if (appData.debugMode)
8812               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8813                       yy_text, (int) moveType);
8814             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8815         }
8816         /* else fall thru */
8817
8818       case XBoardGame:
8819       case GNUChessGame:
8820       case PGNTag:
8821         /* Reached start of next game in file */
8822         if (appData.debugMode)
8823           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8824         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8825           case MT_NONE:
8826           case MT_CHECK:
8827             break;
8828           case MT_CHECKMATE:
8829           case MT_STAINMATE:
8830             if (WhiteOnMove(currentMove)) {
8831                 GameEnds(BlackWins, "Black mates", GE_FILE);
8832             } else {
8833                 GameEnds(WhiteWins, "White mates", GE_FILE);
8834             }
8835             break;
8836           case MT_STALEMATE:
8837             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8838             break;
8839         }
8840         done = TRUE;
8841         break;
8842
8843       case PositionDiagram:     /* should not happen; ignore */
8844       case ElapsedTime:         /* ignore */
8845       case NAG:                 /* ignore */
8846         if (appData.debugMode)
8847           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8848                   yy_text, (int) moveType);
8849         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8850
8851       case IllegalMove:
8852         if (appData.testLegality) {
8853             if (appData.debugMode)
8854               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8855             sprintf(move, _("Illegal move: %d.%s%s"),
8856                     (forwardMostMove / 2) + 1,
8857                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8858             DisplayError(move, 0);
8859             done = TRUE;
8860         } else {
8861             if (appData.debugMode)
8862               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8863                       yy_text, currentMoveString);
8864             fromX = currentMoveString[0] - AAA;
8865             fromY = currentMoveString[1] - ONE;
8866             toX = currentMoveString[2] - AAA;
8867             toY = currentMoveString[3] - ONE;
8868             promoChar = currentMoveString[4];
8869         }
8870         break;
8871
8872       case AmbiguousMove:
8873         if (appData.debugMode)
8874           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8875         sprintf(move, _("Ambiguous move: %d.%s%s"),
8876                 (forwardMostMove / 2) + 1,
8877                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8878         DisplayError(move, 0);
8879         done = TRUE;
8880         break;
8881
8882       default:
8883       case ImpossibleMove:
8884         if (appData.debugMode)
8885           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8886         sprintf(move, _("Illegal move: %d.%s%s"),
8887                 (forwardMostMove / 2) + 1,
8888                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8889         DisplayError(move, 0);
8890         done = TRUE;
8891         break;
8892     }
8893
8894     if (done) {
8895         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8896             DrawPosition(FALSE, boards[currentMove]);
8897             DisplayBothClocks();
8898             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8899               DisplayComment(currentMove - 1, commentList[currentMove]);
8900         }
8901         (void) StopLoadGameTimer();
8902         gameFileFP = NULL;
8903         cmailOldMove = forwardMostMove;
8904         return FALSE;
8905     } else {
8906         /* currentMoveString is set as a side-effect of yylex */
8907         strcat(currentMoveString, "\n");
8908         strcpy(moveList[forwardMostMove], currentMoveString);
8909
8910         thinkOutput[0] = NULLCHAR;
8911         MakeMove(fromX, fromY, toX, toY, promoChar);
8912         currentMove = forwardMostMove;
8913         return TRUE;
8914     }
8915 }
8916
8917 /* Load the nth game from the given file */
8918 int
8919 LoadGameFromFile(filename, n, title, useList)
8920      char *filename;
8921      int n;
8922      char *title;
8923      /*Boolean*/ int useList;
8924 {
8925     FILE *f;
8926     char buf[MSG_SIZ];
8927
8928     if (strcmp(filename, "-") == 0) {
8929         f = stdin;
8930         title = "stdin";
8931     } else {
8932         f = fopen(filename, "rb");
8933         if (f == NULL) {
8934           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8935             DisplayError(buf, errno);
8936             return FALSE;
8937         }
8938     }
8939     if (fseek(f, 0, 0) == -1) {
8940         /* f is not seekable; probably a pipe */
8941         useList = FALSE;
8942     }
8943     if (useList && n == 0) {
8944         int error = GameListBuild(f);
8945         if (error) {
8946             DisplayError(_("Cannot build game list"), error);
8947         } else if (!ListEmpty(&gameList) &&
8948                    ((ListGame *) gameList.tailPred)->number > 1) {
8949           // TODO convert to GTK
8950           //        GameListPopUp(f, title);
8951             return TRUE;
8952         }
8953         GameListDestroy();
8954         n = 1;
8955     }
8956     if (n == 0) n = 1;
8957     return LoadGame(f, n, title, FALSE);
8958 }
8959
8960
8961 void
8962 MakeRegisteredMove()
8963 {
8964     int fromX, fromY, toX, toY;
8965     char promoChar;
8966     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8967         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8968           case CMAIL_MOVE:
8969           case CMAIL_DRAW:
8970             if (appData.debugMode)
8971               fprintf(debugFP, "Restoring %s for game %d\n",
8972                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8973
8974             thinkOutput[0] = NULLCHAR;
8975             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8976             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8977             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8978             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8979             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8980             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8981             MakeMove(fromX, fromY, toX, toY, promoChar);
8982             ShowMove(fromX, fromY, toX, toY);
8983             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8984               case MT_NONE:
8985               case MT_CHECK:
8986                 break;
8987
8988               case MT_CHECKMATE:
8989               case MT_STAINMATE:
8990                 if (WhiteOnMove(currentMove)) {
8991                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8992                 } else {
8993                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8994                 }
8995                 break;
8996
8997               case MT_STALEMATE:
8998                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8999                 break;
9000             }
9001
9002             break;
9003
9004           case CMAIL_RESIGN:
9005             if (WhiteOnMove(currentMove)) {
9006                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9007             } else {
9008                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9009             }
9010             break;
9011
9012           case CMAIL_ACCEPT:
9013             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9014             break;
9015
9016           default:
9017             break;
9018         }
9019     }
9020
9021     return;
9022 }
9023
9024 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9025 int
9026 CmailLoadGame(f, gameNumber, title, useList)
9027      FILE *f;
9028      int gameNumber;
9029      char *title;
9030      int useList;
9031 {
9032     int retVal;
9033
9034     if (gameNumber > nCmailGames) {
9035         DisplayError(_("No more games in this message"), 0);
9036         return FALSE;
9037     }
9038     if (f == lastLoadGameFP) {
9039         int offset = gameNumber - lastLoadGameNumber;
9040         if (offset == 0) {
9041             cmailMsg[0] = NULLCHAR;
9042             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9043                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9044                 nCmailMovesRegistered--;
9045             }
9046             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9047             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9048                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9049             }
9050         } else {
9051             if (! RegisterMove()) return FALSE;
9052         }
9053     }
9054
9055     retVal = LoadGame(f, gameNumber, title, useList);
9056
9057     /* Make move registered during previous look at this game, if any */
9058     MakeRegisteredMove();
9059
9060     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9061         commentList[currentMove]
9062           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9063         DisplayComment(currentMove - 1, commentList[currentMove]);
9064     }
9065
9066     return retVal;
9067 }
9068
9069 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9070 int
9071 ReloadGame(offset)
9072      int offset;
9073 {
9074     int gameNumber = lastLoadGameNumber + offset;
9075     if (lastLoadGameFP == NULL) {
9076         DisplayError(_("No game has been loaded yet"), 0);
9077         return FALSE;
9078     }
9079     if (gameNumber <= 0) {
9080         DisplayError(_("Can't back up any further"), 0);
9081         return FALSE;
9082     }
9083     if (cmailMsgLoaded) {
9084         return CmailLoadGame(lastLoadGameFP, gameNumber,
9085                              lastLoadGameTitle, lastLoadGameUseList);
9086     } else {
9087         return LoadGame(lastLoadGameFP, gameNumber,
9088                         lastLoadGameTitle, lastLoadGameUseList);
9089     }
9090 }
9091
9092
9093
9094 /* Load the nth game from open file f */
9095 int
9096 LoadGame(f, gameNumber, title, useList)
9097      FILE *f;
9098      int gameNumber;
9099      char *title;
9100      int useList;
9101 {
9102     ChessMove cm;
9103     char buf[MSG_SIZ];
9104     int gn = gameNumber;
9105     ListGame *lg = NULL;
9106     int numPGNTags = 0;
9107     int err;
9108     GameMode oldGameMode;
9109     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9110
9111     if (appData.debugMode)
9112         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9113
9114     if (gameMode == Training )
9115         SetTrainingModeOff();
9116
9117     oldGameMode = gameMode;
9118     if (gameMode != BeginningOfGame) 
9119       {
9120         Reset(FALSE, TRUE);
9121       };
9122
9123     gameFileFP = f;
9124     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9125       {
9126         fclose(lastLoadGameFP);
9127       };
9128
9129     if (useList) 
9130       {
9131         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9132         
9133         if (lg) 
9134           {
9135             fseek(f, lg->offset, 0);
9136             GameListHighlight(gameNumber);
9137             gn = 1;
9138           }
9139         else 
9140           {
9141             DisplayError(_("Game number out of range"), 0);
9142             return FALSE;
9143           };
9144       } 
9145     else 
9146       {
9147         GameListDestroy();
9148         if (fseek(f, 0, 0) == -1) 
9149           {
9150             if (f == lastLoadGameFP ?
9151                 gameNumber == lastLoadGameNumber + 1 :
9152                 gameNumber == 1) 
9153               {
9154                 gn = 1;
9155               } 
9156             else 
9157               {
9158                 DisplayError(_("Can't seek on game file"), 0);
9159                 return FALSE;
9160               };
9161           };
9162       };
9163
9164     lastLoadGameFP      = f;
9165     lastLoadGameNumber  = gameNumber;
9166     strcpy(lastLoadGameTitle, title);
9167     lastLoadGameUseList = useList;
9168
9169     yynewfile(f);
9170
9171     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9172       {
9173         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9174                  lg->gameInfo.black);
9175         DisplayTitle(buf);
9176       } 
9177     else if (*title != NULLCHAR) 
9178       {
9179         if (gameNumber > 1) 
9180           {
9181             sprintf(buf, "%s %d", title, gameNumber);
9182             DisplayTitle(buf);
9183           } 
9184         else 
9185           {
9186             DisplayTitle(title);
9187           };
9188       };
9189
9190     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9191       {
9192         gameMode = PlayFromGameFile;
9193         ModeHighlight();
9194       };
9195
9196     currentMove = forwardMostMove = backwardMostMove = 0;
9197     CopyBoard(boards[0], initialPosition);
9198     StopClocks();
9199
9200     /*
9201      * Skip the first gn-1 games in the file.
9202      * Also skip over anything that precedes an identifiable
9203      * start of game marker, to avoid being confused by
9204      * garbage at the start of the file.  Currently
9205      * recognized start of game markers are the move number "1",
9206      * the pattern "gnuchess .* game", the pattern
9207      * "^[#;%] [^ ]* game file", and a PGN tag block.
9208      * A game that starts with one of the latter two patterns
9209      * will also have a move number 1, possibly
9210      * following a position diagram.
9211      * 5-4-02: Let's try being more lenient and allowing a game to
9212      * start with an unnumbered move.  Does that break anything?
9213      */
9214     cm = lastLoadGameStart = (ChessMove) 0;
9215     while (gn > 0) {
9216         yyboardindex = forwardMostMove;
9217         cm = (ChessMove) yylex();
9218         switch (cm) {
9219           case (ChessMove) 0:
9220             if (cmailMsgLoaded) {
9221                 nCmailGames = CMAIL_MAX_GAMES - gn;
9222             } else {
9223                 Reset(TRUE, TRUE);
9224                 DisplayError(_("Game not found in file"), 0);
9225             }
9226             return FALSE;
9227
9228           case GNUChessGame:
9229           case XBoardGame:
9230             gn--;
9231             lastLoadGameStart = cm;
9232             break;
9233
9234           case MoveNumberOne:
9235             switch (lastLoadGameStart) {
9236               case GNUChessGame:
9237               case XBoardGame:
9238               case PGNTag:
9239                 break;
9240               case MoveNumberOne:
9241               case (ChessMove) 0:
9242                 gn--;           /* count this game */
9243                 lastLoadGameStart = cm;
9244                 break;
9245               default:
9246                 /* impossible */
9247                 break;
9248             }
9249             break;
9250
9251           case PGNTag:
9252             switch (lastLoadGameStart) {
9253               case GNUChessGame:
9254               case PGNTag:
9255               case MoveNumberOne:
9256               case (ChessMove) 0:
9257                 gn--;           /* count this game */
9258                 lastLoadGameStart = cm;
9259                 break;
9260               case XBoardGame:
9261                 lastLoadGameStart = cm; /* game counted already */
9262                 break;
9263               default:
9264                 /* impossible */
9265                 break;
9266             }
9267             if (gn > 0) {
9268                 do {
9269                     yyboardindex = forwardMostMove;
9270                     cm = (ChessMove) yylex();
9271                 } while (cm == PGNTag || cm == Comment);
9272             }
9273             break;
9274
9275           case WhiteWins:
9276           case BlackWins:
9277           case GameIsDrawn:
9278             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9279                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9280                     != CMAIL_OLD_RESULT) {
9281                     nCmailResults ++ ;
9282                     cmailResult[  CMAIL_MAX_GAMES
9283                                 - gn - 1] = CMAIL_OLD_RESULT;
9284                 }
9285             }
9286             break;
9287
9288           case NormalMove:
9289             /* Only a NormalMove can be at the start of a game
9290              * without a position diagram. */
9291             if (lastLoadGameStart == (ChessMove) 0) {
9292               gn--;
9293               lastLoadGameStart = MoveNumberOne;
9294             }
9295             break;
9296
9297           default:
9298             break;
9299         }
9300     }
9301
9302     if (appData.debugMode)
9303       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9304
9305     if (cm == XBoardGame) {
9306         /* Skip any header junk before position diagram and/or move 1 */
9307         for (;;) {
9308             yyboardindex = forwardMostMove;
9309             cm = (ChessMove) yylex();
9310
9311             if (cm == (ChessMove) 0 ||
9312                 cm == GNUChessGame || cm == XBoardGame) {
9313                 /* Empty game; pretend end-of-file and handle later */
9314                 cm = (ChessMove) 0;
9315                 break;
9316             }
9317
9318             if (cm == MoveNumberOne || cm == PositionDiagram ||
9319                 cm == PGNTag || cm == Comment)
9320               break;
9321         }
9322     } else if (cm == GNUChessGame) {
9323         if (gameInfo.event != NULL) {
9324             free(gameInfo.event);
9325         }
9326         gameInfo.event = StrSave(yy_text);
9327     }
9328
9329     startedFromSetupPosition = FALSE;
9330     while (cm == PGNTag) {
9331         if (appData.debugMode)
9332           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9333         err = ParsePGNTag(yy_text, &gameInfo);
9334         if (!err) numPGNTags++;
9335
9336         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9337         if(gameInfo.variant != oldVariant) {
9338             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9339             InitPosition(TRUE);
9340             oldVariant = gameInfo.variant;
9341             if (appData.debugMode)
9342               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9343         }
9344
9345
9346         if (gameInfo.fen != NULL) {
9347           Board initial_position;
9348           startedFromSetupPosition = TRUE;
9349           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9350             Reset(TRUE, TRUE);
9351             DisplayError(_("Bad FEN position in file"), 0);
9352             return FALSE;
9353           }
9354           CopyBoard(boards[0], initial_position);
9355           if (blackPlaysFirst) {
9356             currentMove = forwardMostMove = backwardMostMove = 1;
9357             CopyBoard(boards[1], initial_position);
9358             strcpy(moveList[0], "");
9359             strcpy(parseList[0], "");
9360             timeRemaining[0][1] = whiteTimeRemaining;
9361             timeRemaining[1][1] = blackTimeRemaining;
9362             if (commentList[0] != NULL) {
9363               commentList[1] = commentList[0];
9364               commentList[0] = NULL;
9365             }
9366           } else {
9367             currentMove = forwardMostMove = backwardMostMove = 0;
9368           }
9369           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9370           {   int i;
9371               initialRulePlies = FENrulePlies;
9372               for( i=0; i< nrCastlingRights; i++ )
9373                   initialRights[i] = initial_position[CASTLING][i];
9374           }
9375           yyboardindex = forwardMostMove;
9376           free(gameInfo.fen);
9377           gameInfo.fen = NULL;
9378         }
9379
9380         yyboardindex = forwardMostMove;
9381         cm = (ChessMove) yylex();
9382
9383         /* Handle comments interspersed among the tags */
9384         while (cm == Comment) {
9385             char *p;
9386             if (appData.debugMode)
9387               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9388             p = yy_text;
9389             AppendComment(currentMove, p, FALSE);
9390             yyboardindex = forwardMostMove;
9391             cm = (ChessMove) yylex();
9392         }
9393     }
9394
9395     /* don't rely on existence of Event tag since if game was
9396      * pasted from clipboard the Event tag may not exist
9397      */
9398     if (numPGNTags > 0){
9399         char *tags;
9400         if (gameInfo.variant == VariantNormal) {
9401           gameInfo.variant = StringToVariant(gameInfo.event);
9402         }
9403         if (!matchMode) {
9404           if( appData.autoDisplayTags ) {
9405             tags = PGNTags(&gameInfo);
9406             TagsPopUp(tags, CmailMsg());
9407             free(tags);
9408           }
9409         }
9410     } else {
9411         /* Make something up, but don't display it now */
9412         SetGameInfo();
9413         TagsPopDown();
9414     }
9415
9416     if (cm == PositionDiagram) {
9417         int i, j;
9418         char *p;
9419         Board initial_position;
9420
9421         if (appData.debugMode)
9422           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9423
9424         if (!startedFromSetupPosition) {
9425             p = yy_text;
9426             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9427               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9428                 switch (*p) {
9429                   case '[':
9430                   case '-':
9431                   case ' ':
9432                   case '\t':
9433                   case '\n':
9434                   case '\r':
9435                     break;
9436                   default:
9437                     initial_position[i][j++] = CharToPiece(*p);
9438                     break;
9439                 }
9440             while (*p == ' ' || *p == '\t' ||
9441                    *p == '\n' || *p == '\r') p++;
9442
9443             if (strncmp(p, "black", strlen("black"))==0)
9444               blackPlaysFirst = TRUE;
9445             else
9446               blackPlaysFirst = FALSE;
9447             startedFromSetupPosition = TRUE;
9448
9449             CopyBoard(boards[0], initial_position);
9450             if (blackPlaysFirst) {
9451                 currentMove = forwardMostMove = backwardMostMove = 1;
9452                 CopyBoard(boards[1], initial_position);
9453                 strcpy(moveList[0], "");
9454                 strcpy(parseList[0], "");
9455                 timeRemaining[0][1] = whiteTimeRemaining;
9456                 timeRemaining[1][1] = blackTimeRemaining;
9457                 if (commentList[0] != NULL) {
9458                     commentList[1] = commentList[0];
9459                     commentList[0] = NULL;
9460                 }
9461             } else {
9462                 currentMove = forwardMostMove = backwardMostMove = 0;
9463             }
9464         }
9465         yyboardindex = forwardMostMove;
9466         cm = (ChessMove) yylex();
9467     }
9468
9469     if (first.pr == NoProc) {
9470         StartChessProgram(&first);
9471     }
9472     InitChessProgram(&first, FALSE);
9473     SendToProgram("force\n", &first);
9474     if (startedFromSetupPosition) {
9475         SendBoard(&first, forwardMostMove);
9476     if (appData.debugMode) {
9477         fprintf(debugFP, "Load Game\n");
9478     }
9479         DisplayBothClocks();
9480     }
9481
9482     /* [HGM] server: flag to write setup moves in broadcast file as one */
9483     loadFlag = appData.suppressLoadMoves;
9484
9485     while (cm == Comment) {
9486         char *p;
9487         if (appData.debugMode)
9488           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9489         p = yy_text;
9490         AppendComment(currentMove, p, FALSE);
9491         yyboardindex = forwardMostMove;
9492         cm = (ChessMove) yylex();
9493     }
9494
9495     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9496         cm == WhiteWins || cm == BlackWins ||
9497         cm == GameIsDrawn || cm == GameUnfinished) {
9498         DisplayMessage("", _("No moves in game"));
9499         if (cmailMsgLoaded) {
9500             if (appData.debugMode)
9501               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9502             ClearHighlights();
9503             flipView = FALSE;
9504         }
9505         DrawPosition(FALSE, boards[currentMove]);
9506         DisplayBothClocks();
9507         gameMode = EditGame;
9508         ModeHighlight();
9509         gameFileFP = NULL;
9510         cmailOldMove = 0;
9511         return TRUE;
9512     }
9513
9514     // [HGM] PV info: routine tests if comment empty
9515     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9516         DisplayComment(currentMove - 1, commentList[currentMove]);
9517     }
9518     if (!matchMode && appData.timeDelay != 0)
9519       DrawPosition(FALSE, boards[currentMove]);
9520
9521     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9522       programStats.ok_to_send = 1;
9523     }
9524
9525     /* if the first token after the PGN tags is a move
9526      * and not move number 1, retrieve it from the parser
9527      */
9528     if (cm != MoveNumberOne)
9529         LoadGameOneMove(cm);
9530
9531     /* load the remaining moves from the file */
9532     while (LoadGameOneMove((ChessMove)0)) {
9533       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9534       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9535     }
9536
9537     /* rewind to the start of the game */
9538     currentMove = backwardMostMove;
9539
9540     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9541
9542     if (oldGameMode == AnalyzeFile ||
9543         oldGameMode == AnalyzeMode) {
9544       AnalyzeFileEvent();
9545     }
9546
9547     if (matchMode || appData.timeDelay == 0) {
9548       ToEndEvent();
9549       gameMode = EditGame;
9550       ModeHighlight();
9551     } else if (appData.timeDelay > 0) {
9552       AutoPlayGameLoop();
9553     }
9554
9555     if (appData.debugMode)
9556         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9557
9558     loadFlag = 0; /* [HGM] true game starts */
9559     return TRUE;
9560 }
9561
9562 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9563 int
9564 ReloadPosition(offset)
9565      int offset;
9566 {
9567     int positionNumber = lastLoadPositionNumber + offset;
9568     if (lastLoadPositionFP == NULL) {
9569         DisplayError(_("No position has been loaded yet"), 0);
9570         return FALSE;
9571     }
9572     if (positionNumber <= 0) {
9573         DisplayError(_("Can't back up any further"), 0);
9574         return FALSE;
9575     }
9576     return LoadPosition(lastLoadPositionFP, positionNumber,
9577                         lastLoadPositionTitle);
9578 }
9579
9580 /* Load the nth position from the given file */
9581 int
9582 LoadPositionFromFile(filename, n, title)
9583      char *filename;
9584      int n;
9585      char *title;
9586 {
9587     FILE *f;
9588     char buf[MSG_SIZ];
9589
9590     if (strcmp(filename, "-") == 0) {
9591         return LoadPosition(stdin, n, "stdin");
9592     } else {
9593         f = fopen(filename, "rb");
9594         if (f == NULL) {
9595             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9596             DisplayError(buf, errno);
9597             return FALSE;
9598         } else {
9599             return LoadPosition(f, n, title);
9600         }
9601     }
9602 }
9603
9604 /* Load the nth position from the given open file, and close it */
9605 int
9606 LoadPosition(f, positionNumber, title)
9607      FILE *f;
9608      int positionNumber;
9609      char *title;
9610 {
9611     char *p, line[MSG_SIZ];
9612     Board initial_position;
9613     int i, j, fenMode, pn;
9614
9615     if (gameMode == Training )
9616         SetTrainingModeOff();
9617
9618     if (gameMode != BeginningOfGame) {
9619         Reset(FALSE, TRUE);
9620     }
9621     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9622         fclose(lastLoadPositionFP);
9623     }
9624     if (positionNumber == 0) positionNumber = 1;
9625     lastLoadPositionFP = f;
9626     lastLoadPositionNumber = positionNumber;
9627     strcpy(lastLoadPositionTitle, title);
9628     if (first.pr == NoProc) {
9629       StartChessProgram(&first);
9630       InitChessProgram(&first, FALSE);
9631     }
9632     pn = positionNumber;
9633     if (positionNumber < 0) {
9634         /* Negative position number means to seek to that byte offset */
9635         if (fseek(f, -positionNumber, 0) == -1) {
9636             DisplayError(_("Can't seek on position file"), 0);
9637             return FALSE;
9638         };
9639         pn = 1;
9640     } else {
9641         if (fseek(f, 0, 0) == -1) {
9642             if (f == lastLoadPositionFP ?
9643                 positionNumber == lastLoadPositionNumber + 1 :
9644                 positionNumber == 1) {
9645                 pn = 1;
9646             } else {
9647                 DisplayError(_("Can't seek on position file"), 0);
9648                 return FALSE;
9649             }
9650         }
9651     }
9652     /* See if this file is FEN or old-style xboard */
9653     if (fgets(line, MSG_SIZ, f) == NULL) {
9654         DisplayError(_("Position not found in file"), 0);
9655         return FALSE;
9656     }
9657     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9658     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9659
9660     if (pn >= 2) {
9661         if (fenMode || line[0] == '#') pn--;
9662         while (pn > 0) {
9663             /* skip positions before number pn */
9664             if (fgets(line, MSG_SIZ, f) == NULL) {
9665                 Reset(TRUE, TRUE);
9666                 DisplayError(_("Position not found in file"), 0);
9667                 return FALSE;
9668             }
9669             if (fenMode || line[0] == '#') pn--;
9670         }
9671     }
9672
9673     if (fenMode) {
9674         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9675             DisplayError(_("Bad FEN position in file"), 0);
9676             return FALSE;
9677         }
9678     } else {
9679         (void) fgets(line, MSG_SIZ, f);
9680         (void) fgets(line, MSG_SIZ, f);
9681
9682         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9683             (void) fgets(line, MSG_SIZ, f);
9684             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9685                 if (*p == ' ')
9686                   continue;
9687                 initial_position[i][j++] = CharToPiece(*p);
9688             }
9689         }
9690
9691         blackPlaysFirst = FALSE;
9692         if (!feof(f)) {
9693             (void) fgets(line, MSG_SIZ, f);
9694             if (strncmp(line, "black", strlen("black"))==0)
9695               blackPlaysFirst = TRUE;
9696         }
9697     }
9698     startedFromSetupPosition = TRUE;
9699
9700     SendToProgram("force\n", &first);
9701     CopyBoard(boards[0], initial_position);
9702     if (blackPlaysFirst) {
9703         currentMove = forwardMostMove = backwardMostMove = 1;
9704         strcpy(moveList[0], "");
9705         strcpy(parseList[0], "");
9706         CopyBoard(boards[1], initial_position);
9707         DisplayMessage("", _("Black to play"));
9708     } else {
9709         currentMove = forwardMostMove = backwardMostMove = 0;
9710         DisplayMessage("", _("White to play"));
9711     }
9712     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9713     SendBoard(&first, forwardMostMove);
9714     if (appData.debugMode) {
9715 int i, j;
9716   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9717   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9718         fprintf(debugFP, "Load Position\n");
9719     }
9720
9721     if (positionNumber > 1) {
9722         sprintf(line, "%s %d", title, positionNumber);
9723         DisplayTitle(line);
9724     } else {
9725         DisplayTitle(title);
9726     }
9727     gameMode = EditGame;
9728     ModeHighlight();
9729     ResetClocks();
9730     timeRemaining[0][1] = whiteTimeRemaining;
9731     timeRemaining[1][1] = blackTimeRemaining;
9732     DrawPosition(FALSE, boards[currentMove]);
9733
9734     return TRUE;
9735 }
9736
9737
9738 void
9739 CopyPlayerNameIntoFileName(dest, src)
9740      char **dest, *src;
9741 {
9742     while (*src != NULLCHAR && *src != ',') {
9743         if (*src == ' ') {
9744             *(*dest)++ = '_';
9745             src++;
9746         } else {
9747             *(*dest)++ = *src++;
9748         }
9749     }
9750 }
9751
9752 char *DefaultFileName(ext)
9753      char *ext;
9754 {
9755     static char def[MSG_SIZ];
9756     char *p;
9757
9758     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9759         p = def;
9760         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9761         *p++ = '-';
9762         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9763         *p++ = '.';
9764         strcpy(p, ext);
9765     } else {
9766         def[0] = NULLCHAR;
9767     }
9768     return def;
9769 }
9770
9771 /* Save the current game to the given file */
9772 int
9773 SaveGameToFile(filename, append)
9774      char *filename;
9775      int append;
9776 {
9777     FILE *f;
9778     char buf[MSG_SIZ];
9779
9780     if (strcmp(filename, "-") == 0) {
9781         return SaveGame(stdout, 0, NULL);
9782     } else {
9783         f = fopen(filename, append ? "a" : "w");
9784         if (f == NULL) {
9785             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9786             DisplayError(buf, errno);
9787             return FALSE;
9788         } else {
9789             return SaveGame(f, 0, NULL);
9790         }
9791     }
9792 }
9793
9794 char *
9795 SavePart(str)
9796      char *str;
9797 {
9798     static char buf[MSG_SIZ];
9799     char *p;
9800
9801     p = strchr(str, ' ');
9802     if (p == NULL) return str;
9803     strncpy(buf, str, p - str);
9804     buf[p - str] = NULLCHAR;
9805     return buf;
9806 }
9807
9808 #define PGN_MAX_LINE 75
9809
9810 #define PGN_SIDE_WHITE  0
9811 #define PGN_SIDE_BLACK  1
9812
9813 /* [AS] */
9814 static int FindFirstMoveOutOfBook( int side )
9815 {
9816     int result = -1;
9817
9818     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9819         int index = backwardMostMove;
9820         int has_book_hit = 0;
9821
9822         if( (index % 2) != side ) {
9823             index++;
9824         }
9825
9826         while( index < forwardMostMove ) {
9827             /* Check to see if engine is in book */
9828             int depth = pvInfoList[index].depth;
9829             int score = pvInfoList[index].score;
9830             int in_book = 0;
9831
9832             if( depth <= 2 ) {
9833                 in_book = 1;
9834             }
9835             else if( score == 0 && depth == 63 ) {
9836                 in_book = 1; /* Zappa */
9837             }
9838             else if( score == 2 && depth == 99 ) {
9839                 in_book = 1; /* Abrok */
9840             }
9841
9842             has_book_hit += in_book;
9843
9844             if( ! in_book ) {
9845                 result = index;
9846
9847                 break;
9848             }
9849
9850             index += 2;
9851         }
9852     }
9853
9854     return result;
9855 }
9856
9857 /* [AS] */
9858 void GetOutOfBookInfo( char * buf )
9859 {
9860     int oob[2];
9861     int i;
9862     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9863
9864     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9865     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9866
9867     *buf = '\0';
9868
9869     if( oob[0] >= 0 || oob[1] >= 0 ) {
9870         for( i=0; i<2; i++ ) {
9871             int idx = oob[i];
9872
9873             if( idx >= 0 ) {
9874                 if( i > 0 && oob[0] >= 0 ) {
9875                     strcat( buf, "   " );
9876                 }
9877
9878                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9879                 sprintf( buf+strlen(buf), "%s%.2f",
9880                     pvInfoList[idx].score >= 0 ? "+" : "",
9881                     pvInfoList[idx].score / 100.0 );
9882             }
9883         }
9884     }
9885 }
9886
9887 /* Save game in PGN style and close the file */
9888 int
9889 SaveGamePGN(f)
9890      FILE *f;
9891 {
9892     int i, offset, linelen, newblock;
9893     time_t tm;
9894 //    char *movetext;
9895     char numtext[32];
9896     int movelen, numlen, blank;
9897     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9898
9899     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9900
9901     tm = time((time_t *) NULL);
9902
9903     PrintPGNTags(f, &gameInfo);
9904
9905     if (backwardMostMove > 0 || startedFromSetupPosition) {
9906         char *fen = PositionToFEN(backwardMostMove, NULL);
9907         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9908         fprintf(f, "\n{--------------\n");
9909         PrintPosition(f, backwardMostMove);
9910         fprintf(f, "--------------}\n");
9911         free(fen);
9912     }
9913     else {
9914         /* [AS] Out of book annotation */
9915         if( appData.saveOutOfBookInfo ) {
9916             char buf[64];
9917
9918             GetOutOfBookInfo( buf );
9919
9920             if( buf[0] != '\0' ) {
9921                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9922             }
9923         }
9924
9925         fprintf(f, "\n");
9926     }
9927
9928     i = backwardMostMove;
9929     linelen = 0;
9930     newblock = TRUE;
9931
9932     while (i < forwardMostMove) {
9933         /* Print comments preceding this move */
9934         if (commentList[i] != NULL) {
9935             if (linelen > 0) fprintf(f, "\n");
9936             fprintf(f, "%s", commentList[i]);
9937             linelen = 0;
9938             newblock = TRUE;
9939         }
9940
9941         /* Format move number */
9942         if ((i % 2) == 0) {
9943             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9944         } else {
9945             if (newblock) {
9946                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9947             } else {
9948                 numtext[0] = NULLCHAR;
9949             }
9950         }
9951         numlen = strlen(numtext);
9952         newblock = FALSE;
9953
9954         /* Print move number */
9955         blank = linelen > 0 && numlen > 0;
9956         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9957             fprintf(f, "\n");
9958             linelen = 0;
9959             blank = 0;
9960         }
9961         if (blank) {
9962             fprintf(f, " ");
9963             linelen++;
9964         }
9965         fprintf(f, "%s", numtext);
9966         linelen += numlen;
9967
9968         /* Get move */
9969         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9970         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9971
9972         /* Print move */
9973         blank = linelen > 0 && movelen > 0;
9974         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9975             fprintf(f, "\n");
9976             linelen = 0;
9977             blank = 0;
9978         }
9979         if (blank) {
9980             fprintf(f, " ");
9981             linelen++;
9982         }
9983         fprintf(f, "%s", move_buffer);
9984         linelen += movelen;
9985
9986         /* [AS] Add PV info if present */
9987         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9988             /* [HGM] add time */
9989             char buf[MSG_SIZ]; int seconds;
9990
9991             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9992
9993             if( seconds <= 0) buf[0] = 0; else
9994             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9995                 seconds = (seconds + 4)/10; // round to full seconds
9996                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9997                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9998             }
9999
10000             sprintf( move_buffer, "{%s%.2f/%d%s}",
10001                 pvInfoList[i].score >= 0 ? "+" : "",
10002                 pvInfoList[i].score / 100.0,
10003                 pvInfoList[i].depth,
10004                 buf );
10005
10006             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10007
10008             /* Print score/depth */
10009             blank = linelen > 0 && movelen > 0;
10010             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10011                 fprintf(f, "\n");
10012                 linelen = 0;
10013                 blank = 0;
10014             }
10015             if (blank) {
10016                 fprintf(f, " ");
10017                 linelen++;
10018             }
10019             fprintf(f, "%s", move_buffer);
10020             linelen += movelen;
10021         }
10022
10023         i++;
10024     }
10025
10026     /* Start a new line */
10027     if (linelen > 0) fprintf(f, "\n");
10028
10029     /* Print comments after last move */
10030     if (commentList[i] != NULL) {
10031         fprintf(f, "%s\n", commentList[i]);
10032     }
10033
10034     /* Print result */
10035     if (gameInfo.resultDetails != NULL &&
10036         gameInfo.resultDetails[0] != NULLCHAR) {
10037         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10038                 PGNResult(gameInfo.result));
10039     } else {
10040         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10041     }
10042
10043     fclose(f);
10044     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10045     return TRUE;
10046 }
10047
10048 /* Save game in old style and close the file */
10049 int
10050 SaveGameOldStyle(f)
10051      FILE *f;
10052 {
10053     int i, offset;
10054     time_t tm;
10055
10056     tm = time((time_t *) NULL);
10057
10058     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10059     PrintOpponents(f);
10060
10061     if (backwardMostMove > 0 || startedFromSetupPosition) {
10062         fprintf(f, "\n[--------------\n");
10063         PrintPosition(f, backwardMostMove);
10064         fprintf(f, "--------------]\n");
10065     } else {
10066         fprintf(f, "\n");
10067     }
10068
10069     i = backwardMostMove;
10070     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10071
10072     while (i < forwardMostMove) {
10073         if (commentList[i] != NULL) {
10074             fprintf(f, "[%s]\n", commentList[i]);
10075         }
10076
10077         if ((i % 2) == 1) {
10078             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10079             i++;
10080         } else {
10081             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10082             i++;
10083             if (commentList[i] != NULL) {
10084                 fprintf(f, "\n");
10085                 continue;
10086             }
10087             if (i >= forwardMostMove) {
10088                 fprintf(f, "\n");
10089                 break;
10090             }
10091             fprintf(f, "%s\n", parseList[i]);
10092             i++;
10093         }
10094     }
10095
10096     if (commentList[i] != NULL) {
10097         fprintf(f, "[%s]\n", commentList[i]);
10098     }
10099
10100     /* This isn't really the old style, but it's close enough */
10101     if (gameInfo.resultDetails != NULL &&
10102         gameInfo.resultDetails[0] != NULLCHAR) {
10103         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10104                 gameInfo.resultDetails);
10105     } else {
10106         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10107     }
10108
10109     fclose(f);
10110     return TRUE;
10111 }
10112
10113 /* Save the current game to open file f and close the file */
10114 int
10115 SaveGame(f, dummy, dummy2)
10116      FILE *f;
10117      int dummy;
10118      char *dummy2;
10119 {
10120     if (gameMode == EditPosition) EditPositionDone(TRUE);
10121     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10122     if (appData.oldSaveStyle)
10123       return SaveGameOldStyle(f);
10124     else
10125       return SaveGamePGN(f);
10126 }
10127
10128 /* Save the current position to the given file */
10129 int
10130 SavePositionToFile(filename)
10131      char *filename;
10132 {
10133     FILE *f;
10134     char buf[MSG_SIZ];
10135
10136     if (strcmp(filename, "-") == 0) {
10137         return SavePosition(stdout, 0, NULL);
10138     } else {
10139         f = fopen(filename, "a");
10140         if (f == NULL) {
10141             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10142             DisplayError(buf, errno);
10143             return FALSE;
10144         } else {
10145             SavePosition(f, 0, NULL);
10146             return TRUE;
10147         }
10148     }
10149 }
10150
10151 /* Save the current position to the given open file and close the file */
10152 int
10153 SavePosition(f, dummy, dummy2)
10154      FILE *f;
10155      int dummy;
10156      char *dummy2;
10157 {
10158     time_t tm;
10159     char *fen;
10160     if (gameMode == EditPosition) EditPositionDone(TRUE);
10161     if (appData.oldSaveStyle) {
10162         tm = time((time_t *) NULL);
10163
10164         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10165         PrintOpponents(f);
10166         fprintf(f, "[--------------\n");
10167         PrintPosition(f, currentMove);
10168         fprintf(f, "--------------]\n");
10169     } else {
10170         fen = PositionToFEN(currentMove, NULL);
10171         fprintf(f, "%s\n", fen);
10172         free(fen);
10173     }
10174     fclose(f);
10175     return TRUE;
10176 }
10177
10178 void
10179 ReloadCmailMsgEvent(unregister)
10180      int unregister;
10181 {
10182 #if !WIN32
10183     static char *inFilename = NULL;
10184     static char *outFilename;
10185     int i;
10186     struct stat inbuf, outbuf;
10187     int status;
10188
10189     /* Any registered moves are unregistered if unregister is set, */
10190     /* i.e. invoked by the signal handler */
10191     if (unregister) {
10192         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10193             cmailMoveRegistered[i] = FALSE;
10194             if (cmailCommentList[i] != NULL) {
10195                 free(cmailCommentList[i]);
10196                 cmailCommentList[i] = NULL;
10197             }
10198         }
10199         nCmailMovesRegistered = 0;
10200     }
10201
10202     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10203         cmailResult[i] = CMAIL_NOT_RESULT;
10204     }
10205     nCmailResults = 0;
10206
10207     if (inFilename == NULL) {
10208         /* Because the filenames are static they only get malloced once  */
10209         /* and they never get freed                                      */
10210         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10211         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10212
10213         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10214         sprintf(outFilename, "%s.out", appData.cmailGameName);
10215     }
10216
10217     status = stat(outFilename, &outbuf);
10218     if (status < 0) {
10219         cmailMailedMove = FALSE;
10220     } else {
10221         status = stat(inFilename, &inbuf);
10222         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10223     }
10224
10225     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10226        counts the games, notes how each one terminated, etc.
10227
10228        It would be nice to remove this kludge and instead gather all
10229        the information while building the game list.  (And to keep it
10230        in the game list nodes instead of having a bunch of fixed-size
10231        parallel arrays.)  Note this will require getting each game's
10232        termination from the PGN tags, as the game list builder does
10233        not process the game moves.  --mann
10234        */
10235     cmailMsgLoaded = TRUE;
10236     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10237
10238     /* Load first game in the file or popup game menu */
10239     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10240
10241 #endif /* !WIN32 */
10242     return;
10243 }
10244
10245 int
10246 RegisterMove()
10247 {
10248     FILE *f;
10249     char string[MSG_SIZ];
10250
10251     if (   cmailMailedMove
10252         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10253         return TRUE;            /* Allow free viewing  */
10254     }
10255
10256     /* Unregister move to ensure that we don't leave RegisterMove        */
10257     /* with the move registered when the conditions for registering no   */
10258     /* longer hold                                                       */
10259     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10260         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10261         nCmailMovesRegistered --;
10262
10263         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10264           {
10265               free(cmailCommentList[lastLoadGameNumber - 1]);
10266               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10267           }
10268     }
10269
10270     if (cmailOldMove == -1) {
10271         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10272         return FALSE;
10273     }
10274
10275     if (currentMove > cmailOldMove + 1) {
10276         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10277         return FALSE;
10278     }
10279
10280     if (currentMove < cmailOldMove) {
10281         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10282         return FALSE;
10283     }
10284
10285     if (forwardMostMove > currentMove) {
10286         /* Silently truncate extra moves */
10287         TruncateGame();
10288     }
10289
10290     if (   (currentMove == cmailOldMove + 1)
10291         || (   (currentMove == cmailOldMove)
10292             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10293                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10294         if (gameInfo.result != GameUnfinished) {
10295             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10296         }
10297
10298         if (commentList[currentMove] != NULL) {
10299             cmailCommentList[lastLoadGameNumber - 1]
10300               = StrSave(commentList[currentMove]);
10301         }
10302         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10303
10304         if (appData.debugMode)
10305           fprintf(debugFP, "Saving %s for game %d\n",
10306                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10307
10308         sprintf(string,
10309                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10310
10311         f = fopen(string, "w");
10312         if (appData.oldSaveStyle) {
10313             SaveGameOldStyle(f); /* also closes the file */
10314
10315             sprintf(string, "%s.pos.out", appData.cmailGameName);
10316             f = fopen(string, "w");
10317             SavePosition(f, 0, NULL); /* also closes the file */
10318         } else {
10319             fprintf(f, "{--------------\n");
10320             PrintPosition(f, currentMove);
10321             fprintf(f, "--------------}\n\n");
10322
10323             SaveGame(f, 0, NULL); /* also closes the file*/
10324         }
10325
10326         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10327         nCmailMovesRegistered ++;
10328     } else if (nCmailGames == 1) {
10329         DisplayError(_("You have not made a move yet"), 0);
10330         return FALSE;
10331     }
10332
10333     return TRUE;
10334 }
10335
10336 void
10337 MailMoveEvent()
10338 {
10339 #if !WIN32
10340     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10341     FILE *commandOutput;
10342     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10343     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10344     int nBuffers;
10345     int i;
10346     int archived;
10347     char *arcDir;
10348
10349     if (! cmailMsgLoaded) {
10350         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10351         return;
10352     }
10353
10354     if (nCmailGames == nCmailResults) {
10355         DisplayError(_("No unfinished games"), 0);
10356         return;
10357     }
10358
10359 #if CMAIL_PROHIBIT_REMAIL
10360     if (cmailMailedMove) {
10361         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);
10362         DisplayError(msg, 0);
10363         return;
10364     }
10365 #endif
10366
10367     if (! (cmailMailedMove || RegisterMove())) return;
10368
10369     if (   cmailMailedMove
10370         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10371         sprintf(string, partCommandString,
10372                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10373         commandOutput = popen(string, "r");
10374
10375         if (commandOutput == NULL) {
10376             DisplayError(_("Failed to invoke cmail"), 0);
10377         } else {
10378             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10379                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10380             }
10381             if (nBuffers > 1) {
10382                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10383                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10384                 nBytes = MSG_SIZ - 1;
10385             } else {
10386                 (void) memcpy(msg, buffer, nBytes);
10387             }
10388             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10389
10390             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10391                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10392
10393                 archived = TRUE;
10394                 for (i = 0; i < nCmailGames; i ++) {
10395                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10396                         archived = FALSE;
10397                     }
10398                 }
10399                 if (   archived
10400                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10401                         != NULL)) {
10402                     sprintf(buffer, "%s/%s.%s.archive",
10403                             arcDir,
10404                             appData.cmailGameName,
10405                             gameInfo.date);
10406                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10407                     cmailMsgLoaded = FALSE;
10408                 }
10409             }
10410
10411             DisplayInformation(msg);
10412             pclose(commandOutput);
10413         }
10414     } else {
10415         if ((*cmailMsg) != '\0') {
10416             DisplayInformation(cmailMsg);
10417         }
10418     }
10419
10420     return;
10421 #endif /* !WIN32 */
10422 }
10423
10424 char *
10425 CmailMsg()
10426 {
10427 #if WIN32
10428     return NULL;
10429 #else
10430     int  prependComma = 0;
10431     char number[5];
10432     char string[MSG_SIZ];       /* Space for game-list */
10433     int  i;
10434
10435     if (!cmailMsgLoaded) return "";
10436
10437     if (cmailMailedMove) {
10438         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10439     } else {
10440         /* Create a list of games left */
10441         sprintf(string, "[");
10442         for (i = 0; i < nCmailGames; i ++) {
10443             if (! (   cmailMoveRegistered[i]
10444                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10445                 if (prependComma) {
10446                     sprintf(number, ",%d", i + 1);
10447                 } else {
10448                     sprintf(number, "%d", i + 1);
10449                     prependComma = 1;
10450                 }
10451
10452                 strcat(string, number);
10453             }
10454         }
10455         strcat(string, "]");
10456
10457         if (nCmailMovesRegistered + nCmailResults == 0) {
10458             switch (nCmailGames) {
10459               case 1:
10460                 sprintf(cmailMsg,
10461                         _("Still need to make move for game\n"));
10462                 break;
10463
10464               case 2:
10465                 sprintf(cmailMsg,
10466                         _("Still need to make moves for both games\n"));
10467                 break;
10468
10469               default:
10470                 sprintf(cmailMsg,
10471                         _("Still need to make moves for all %d games\n"),
10472                         nCmailGames);
10473                 break;
10474             }
10475         } else {
10476             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10477               case 1:
10478                 sprintf(cmailMsg,
10479                         _("Still need to make a move for game %s\n"),
10480                         string);
10481                 break;
10482
10483               case 0:
10484                 if (nCmailResults == nCmailGames) {
10485                     sprintf(cmailMsg, _("No unfinished games\n"));
10486                 } else {
10487                     sprintf(cmailMsg, _("Ready to send mail\n"));
10488                 }
10489                 break;
10490
10491               default:
10492                 sprintf(cmailMsg,
10493                         _("Still need to make moves for games %s\n"),
10494                         string);
10495             }
10496         }
10497     }
10498     return cmailMsg;
10499 #endif /* WIN32 */
10500 }
10501
10502 void
10503 ResetGameEvent()
10504 {
10505     if (gameMode == Training)
10506       SetTrainingModeOff();
10507
10508     Reset(TRUE, TRUE);
10509     cmailMsgLoaded = FALSE;
10510     if (appData.icsActive) {
10511       SendToICS(ics_prefix);
10512       SendToICS("refresh\n");
10513     }
10514 }
10515
10516 void
10517 ExitEvent(status)
10518      int status;
10519 {
10520     exiting++;
10521     if (exiting > 2) {
10522       /* Give up on clean exit */
10523       exit(status);
10524     }
10525     if (exiting > 1) {
10526       /* Keep trying for clean exit */
10527       return;
10528     }
10529
10530     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10531
10532     if (telnetISR != NULL) {
10533       RemoveInputSource(telnetISR);
10534     }
10535     if (icsPR != NoProc) {
10536       DestroyChildProcess(icsPR, TRUE);
10537     }
10538
10539     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10540     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10541
10542     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10543     /* make sure this other one finishes before killing it!                  */
10544     if(endingGame) { int count = 0;
10545         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10546         while(endingGame && count++ < 10) DoSleep(1);
10547         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10548     }
10549
10550     /* Kill off chess programs */
10551     if (first.pr != NoProc) {
10552         ExitAnalyzeMode();
10553
10554         DoSleep( appData.delayBeforeQuit );
10555         SendToProgram("quit\n", &first);
10556         DoSleep( appData.delayAfterQuit );
10557         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10558     }
10559     if (second.pr != NoProc) {
10560         DoSleep( appData.delayBeforeQuit );
10561         SendToProgram("quit\n", &second);
10562         DoSleep( appData.delayAfterQuit );
10563         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10564     }
10565     if (first.isr != NULL) {
10566         RemoveInputSource(first.isr);
10567     }
10568     if (second.isr != NULL) {
10569         RemoveInputSource(second.isr);
10570     }
10571
10572     ShutDownFrontEnd();
10573     exit(status);
10574 }
10575
10576 void
10577 PauseEvent()
10578 {
10579     if (appData.debugMode)
10580         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10581     if (pausing) {
10582         pausing = FALSE;
10583         ModeHighlight();
10584         if (gameMode == MachinePlaysWhite ||
10585             gameMode == MachinePlaysBlack) {
10586             StartClocks();
10587         } else {
10588             DisplayBothClocks();
10589         }
10590         if (gameMode == PlayFromGameFile) {
10591             if (appData.timeDelay >= 0)
10592                 AutoPlayGameLoop();
10593         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10594             Reset(FALSE, TRUE);
10595             SendToICS(ics_prefix);
10596             SendToICS("refresh\n");
10597         } else if (currentMove < forwardMostMove) {
10598             ForwardInner(forwardMostMove);
10599         }
10600         pauseExamInvalid = FALSE;
10601     } else {
10602         switch (gameMode) {
10603           default:
10604             return;
10605           case IcsExamining:
10606             pauseExamForwardMostMove = forwardMostMove;
10607             pauseExamInvalid = FALSE;
10608             /* fall through */
10609           case IcsObserving:
10610           case IcsPlayingWhite:
10611           case IcsPlayingBlack:
10612             pausing = TRUE;
10613             ModeHighlight();
10614             return;
10615           case PlayFromGameFile:
10616             (void) StopLoadGameTimer();
10617             pausing = TRUE;
10618             ModeHighlight();
10619             break;
10620           case BeginningOfGame:
10621             if (appData.icsActive) return;
10622             /* else fall through */
10623           case MachinePlaysWhite:
10624           case MachinePlaysBlack:
10625           case TwoMachinesPlay:
10626             if (forwardMostMove == 0)
10627               return;           /* don't pause if no one has moved */
10628             if ((gameMode == MachinePlaysWhite &&
10629                  !WhiteOnMove(forwardMostMove)) ||
10630                 (gameMode == MachinePlaysBlack &&
10631                  WhiteOnMove(forwardMostMove))) {
10632                 StopClocks();
10633             }
10634             pausing = TRUE;
10635             ModeHighlight();
10636             break;
10637         }
10638     }
10639 }
10640
10641 void
10642 EditCommentEvent()
10643 {
10644     char title[MSG_SIZ];
10645
10646     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10647         strcpy(title, _("Edit comment"));
10648     } else {
10649         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10650                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10651                 parseList[currentMove - 1]);
10652     }
10653
10654     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10655 }
10656
10657
10658 void
10659 EditTagsEvent()
10660 {
10661     char *tags = PGNTags(&gameInfo);
10662     EditTagsPopUp(tags);
10663     free(tags);
10664 }
10665
10666 void
10667 AnalyzeModeEvent()
10668 {
10669     if (appData.noChessProgram || gameMode == AnalyzeMode)
10670       return;
10671
10672     if (gameMode != AnalyzeFile) {
10673         if (!appData.icsEngineAnalyze) {
10674                EditGameEvent();
10675                if (gameMode != EditGame) return;
10676         }
10677         ResurrectChessProgram();
10678         SendToProgram("analyze\n", &first);
10679         first.analyzing = TRUE;
10680         /*first.maybeThinking = TRUE;*/
10681         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10682         EngineOutputPopUp();
10683     }
10684     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10685     pausing = FALSE;
10686     ModeHighlight();
10687     SetGameInfo();
10688
10689     StartAnalysisClock();
10690     GetTimeMark(&lastNodeCountTime);
10691     lastNodeCount = 0;
10692 }
10693
10694 void
10695 AnalyzeFileEvent()
10696 {
10697     if (appData.noChessProgram || gameMode == AnalyzeFile)
10698       return;
10699
10700     if (gameMode != AnalyzeMode) {
10701         EditGameEvent();
10702         if (gameMode != EditGame) return;
10703         ResurrectChessProgram();
10704         SendToProgram("analyze\n", &first);
10705         first.analyzing = TRUE;
10706         /*first.maybeThinking = TRUE;*/
10707         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10708         EngineOutputPopUp();
10709     }
10710     gameMode = AnalyzeFile;
10711     pausing = FALSE;
10712     ModeHighlight();
10713     SetGameInfo();
10714
10715     StartAnalysisClock();
10716     GetTimeMark(&lastNodeCountTime);
10717     lastNodeCount = 0;
10718 }
10719
10720 void
10721 MachineWhiteEvent()
10722 {
10723     char buf[MSG_SIZ];
10724     char *bookHit = NULL;
10725
10726     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10727       return;
10728
10729
10730     if (gameMode == PlayFromGameFile ||
10731         gameMode == TwoMachinesPlay  ||
10732         gameMode == Training         ||
10733         gameMode == AnalyzeMode      ||
10734         gameMode == EndOfGame)
10735         EditGameEvent();
10736
10737     if (gameMode == EditPosition) 
10738         EditPositionDone(TRUE);
10739
10740     if (!WhiteOnMove(currentMove)) {
10741         DisplayError(_("It is not White's turn"), 0);
10742         return;
10743     }
10744
10745     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10746       ExitAnalyzeMode();
10747
10748     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10749         gameMode == AnalyzeFile)
10750         TruncateGame();
10751
10752     ResurrectChessProgram();    /* in case it isn't running */
10753     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10754         gameMode = MachinePlaysWhite;
10755         ResetClocks();
10756     } else
10757     gameMode = MachinePlaysWhite;
10758     pausing = FALSE;
10759     ModeHighlight();
10760     SetGameInfo();
10761     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10762     DisplayTitle(buf);
10763     if (first.sendName) {
10764       sprintf(buf, "name %s\n", gameInfo.black);
10765       SendToProgram(buf, &first);
10766     }
10767     if (first.sendTime) {
10768       if (first.useColors) {
10769         SendToProgram("black\n", &first); /*gnu kludge*/
10770       }
10771       SendTimeRemaining(&first, TRUE);
10772     }
10773     if (first.useColors) {
10774       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10775     }
10776     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10777     SetMachineThinkingEnables();
10778     first.maybeThinking = TRUE;
10779     StartClocks();
10780     firstMove = FALSE;
10781
10782     if (appData.autoFlipView && !flipView) {
10783       flipView = !flipView;
10784       DrawPosition(FALSE, NULL);
10785       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10786     }
10787
10788     if(bookHit) { // [HGM] book: simulate book reply
10789         static char bookMove[MSG_SIZ]; // a bit generous?
10790
10791         programStats.nodes = programStats.depth = programStats.time =
10792         programStats.score = programStats.got_only_move = 0;
10793         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10794
10795         strcpy(bookMove, "move ");
10796         strcat(bookMove, bookHit);
10797         HandleMachineMove(bookMove, &first);
10798     }
10799 }
10800
10801 void
10802 MachineBlackEvent()
10803 {
10804     char buf[MSG_SIZ];
10805    char *bookHit = NULL;
10806
10807     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10808         return;
10809
10810
10811     if (gameMode == PlayFromGameFile ||
10812         gameMode == TwoMachinesPlay  ||
10813         gameMode == Training         ||
10814         gameMode == AnalyzeMode      ||
10815         gameMode == EndOfGame)
10816         EditGameEvent();
10817
10818     if (gameMode == EditPosition) 
10819         EditPositionDone(TRUE);
10820
10821     if (WhiteOnMove(currentMove)) {
10822         DisplayError(_("It is not Black's turn"), 0);
10823         return;
10824     }
10825
10826     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10827       ExitAnalyzeMode();
10828
10829     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10830         gameMode == AnalyzeFile)
10831         TruncateGame();
10832
10833     ResurrectChessProgram();    /* in case it isn't running */
10834     gameMode = MachinePlaysBlack;
10835     pausing = FALSE;
10836     ModeHighlight();
10837     SetGameInfo();
10838     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10839     DisplayTitle(buf);
10840     if (first.sendName) {
10841       sprintf(buf, "name %s\n", gameInfo.white);
10842       SendToProgram(buf, &first);
10843     }
10844     if (first.sendTime) {
10845       if (first.useColors) {
10846         SendToProgram("white\n", &first); /*gnu kludge*/
10847       }
10848       SendTimeRemaining(&first, FALSE);
10849     }
10850     if (first.useColors) {
10851       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10852     }
10853     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10854     SetMachineThinkingEnables();
10855     first.maybeThinking = TRUE;
10856     StartClocks();
10857
10858     if (appData.autoFlipView && flipView) {
10859       flipView = !flipView;
10860       DrawPosition(FALSE, NULL);
10861       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10862     }
10863     if(bookHit) { // [HGM] book: simulate book reply
10864         static char bookMove[MSG_SIZ]; // a bit generous?
10865
10866         programStats.nodes = programStats.depth = programStats.time =
10867         programStats.score = programStats.got_only_move = 0;
10868         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10869
10870         strcpy(bookMove, "move ");
10871         strcat(bookMove, bookHit);
10872         HandleMachineMove(bookMove, &first);
10873     }
10874 }
10875
10876
10877 void
10878 DisplayTwoMachinesTitle()
10879 {
10880     char buf[MSG_SIZ];
10881     if (appData.matchGames > 0) {
10882         if (first.twoMachinesColor[0] == 'w') {
10883             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10884                     gameInfo.white, gameInfo.black,
10885                     first.matchWins, second.matchWins,
10886                     matchGame - 1 - (first.matchWins + second.matchWins));
10887         } else {
10888             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10889                     gameInfo.white, gameInfo.black,
10890                     second.matchWins, first.matchWins,
10891                     matchGame - 1 - (first.matchWins + second.matchWins));
10892         }
10893     } else {
10894         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10895     }
10896     DisplayTitle(buf);
10897 }
10898
10899 void
10900 TwoMachinesEvent P((void))
10901 {
10902     int i;
10903     char buf[MSG_SIZ];
10904     ChessProgramState *onmove;
10905     char *bookHit = NULL;
10906
10907     if (appData.noChessProgram) return;
10908
10909     switch (gameMode) {
10910       case TwoMachinesPlay:
10911         return;
10912       case MachinePlaysWhite:
10913       case MachinePlaysBlack:
10914         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10915             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10916             return;
10917         }
10918         /* fall through */
10919       case BeginningOfGame:
10920       case PlayFromGameFile:
10921       case EndOfGame:
10922         EditGameEvent();
10923         if (gameMode != EditGame) return;
10924         break;
10925       case EditPosition:
10926         EditPositionDone(TRUE);
10927         break;
10928       case AnalyzeMode:
10929       case AnalyzeFile:
10930         ExitAnalyzeMode();
10931         break;
10932       case EditGame:
10933       default:
10934         break;
10935     }
10936
10937 //    forwardMostMove = currentMove;
10938     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10939     ResurrectChessProgram();    /* in case first program isn't running */
10940
10941     if (second.pr == NULL) {
10942         StartChessProgram(&second);
10943         if (second.protocolVersion == 1) {
10944           TwoMachinesEventIfReady();
10945         } else {
10946           /* kludge: allow timeout for initial "feature" command */
10947           FreezeUI();
10948           DisplayMessage("", _("Starting second chess program"));
10949           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10950         }
10951         return;
10952     }
10953     DisplayMessage("", "");
10954     InitChessProgram(&second, FALSE);
10955     SendToProgram("force\n", &second);
10956     if (startedFromSetupPosition) {
10957         SendBoard(&second, backwardMostMove);
10958     if (appData.debugMode) {
10959         fprintf(debugFP, "Two Machines\n");
10960     }
10961     }
10962     for (i = backwardMostMove; i < forwardMostMove; i++) {
10963         SendMoveToProgram(i, &second);
10964     }
10965
10966     gameMode = TwoMachinesPlay;
10967     pausing = FALSE;
10968     ModeHighlight();
10969     SetGameInfo();
10970     DisplayTwoMachinesTitle();
10971     firstMove = TRUE;
10972     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10973         onmove = &first;
10974     } else {
10975         onmove = &second;
10976     }
10977
10978     SendToProgram(first.computerString, &first);
10979     if (first.sendName) {
10980       sprintf(buf, "name %s\n", second.tidy);
10981       SendToProgram(buf, &first);
10982     }
10983     SendToProgram(second.computerString, &second);
10984     if (second.sendName) {
10985       sprintf(buf, "name %s\n", first.tidy);
10986       SendToProgram(buf, &second);
10987     }
10988
10989     ResetClocks();
10990     if (!first.sendTime || !second.sendTime) {
10991         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10992         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10993     }
10994     if (onmove->sendTime) {
10995       if (onmove->useColors) {
10996         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10997       }
10998       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10999     }
11000     if (onmove->useColors) {
11001       SendToProgram(onmove->twoMachinesColor, onmove);
11002     }
11003     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11004 //    SendToProgram("go\n", onmove);
11005     onmove->maybeThinking = TRUE;
11006     SetMachineThinkingEnables();
11007
11008     StartClocks();
11009
11010     if(bookHit) { // [HGM] book: simulate book reply
11011         static char bookMove[MSG_SIZ]; // a bit generous?
11012
11013         programStats.nodes = programStats.depth = programStats.time =
11014         programStats.score = programStats.got_only_move = 0;
11015         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11016
11017         strcpy(bookMove, "move ");
11018         strcat(bookMove, bookHit);
11019         savedMessage = bookMove; // args for deferred call
11020         savedState = onmove;
11021         ScheduleDelayedEvent(DeferredBookMove, 1);
11022     }
11023 }
11024
11025 void
11026 TrainingEvent()
11027 {
11028     if (gameMode == Training) {
11029       SetTrainingModeOff();
11030       gameMode = PlayFromGameFile;
11031       DisplayMessage("", _("Training mode off"));
11032     } else {
11033       gameMode = Training;
11034       animateTraining = appData.animate;
11035
11036       /* make sure we are not already at the end of the game */
11037       if (currentMove < forwardMostMove) {
11038         SetTrainingModeOn();
11039         DisplayMessage("", _("Training mode on"));
11040       } else {
11041         gameMode = PlayFromGameFile;
11042         DisplayError(_("Already at end of game"), 0);
11043       }
11044     }
11045     ModeHighlight();
11046 }
11047
11048 void
11049 IcsClientEvent()
11050 {
11051     if (!appData.icsActive) return;
11052     switch (gameMode) {
11053       case IcsPlayingWhite:
11054       case IcsPlayingBlack:
11055       case IcsObserving:
11056       case IcsIdle:
11057       case BeginningOfGame:
11058       case IcsExamining:
11059         return;
11060
11061       case EditGame:
11062         break;
11063
11064       case EditPosition:
11065         EditPositionDone(TRUE);
11066         break;
11067
11068       case AnalyzeMode:
11069       case AnalyzeFile:
11070         ExitAnalyzeMode();
11071         break;
11072
11073       default:
11074         EditGameEvent();
11075         break;
11076     }
11077
11078     gameMode = IcsIdle;
11079     ModeHighlight();
11080     return;
11081 }
11082
11083
11084 void
11085 EditGameEvent()
11086 {
11087     int i;
11088
11089     switch (gameMode) {
11090       case Training:
11091         SetTrainingModeOff();
11092         break;
11093       case MachinePlaysWhite:
11094       case MachinePlaysBlack:
11095       case BeginningOfGame:
11096         SendToProgram("force\n", &first);
11097         SetUserThinkingEnables();
11098         break;
11099       case PlayFromGameFile:
11100         (void) StopLoadGameTimer();
11101         if (gameFileFP != NULL) {
11102             gameFileFP = NULL;
11103         }
11104         break;
11105       case EditPosition:
11106         EditPositionDone(TRUE);
11107         break;
11108       case AnalyzeMode:
11109       case AnalyzeFile:
11110         ExitAnalyzeMode();
11111         SendToProgram("force\n", &first);
11112         break;
11113       case TwoMachinesPlay:
11114         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11115         ResurrectChessProgram();
11116         SetUserThinkingEnables();
11117         break;
11118       case EndOfGame:
11119         ResurrectChessProgram();
11120         break;
11121       case IcsPlayingBlack:
11122       case IcsPlayingWhite:
11123         DisplayError(_("Warning: You are still playing a game"), 0);
11124         break;
11125       case IcsObserving:
11126         DisplayError(_("Warning: You are still observing a game"), 0);
11127         break;
11128       case IcsExamining:
11129         DisplayError(_("Warning: You are still examining a game"), 0);
11130         break;
11131       case IcsIdle:
11132         break;
11133       case EditGame:
11134       default:
11135         return;
11136     }
11137
11138     pausing = FALSE;
11139     StopClocks();
11140     first.offeredDraw = second.offeredDraw = 0;
11141
11142     if (gameMode == PlayFromGameFile) {
11143         whiteTimeRemaining = timeRemaining[0][currentMove];
11144         blackTimeRemaining = timeRemaining[1][currentMove];
11145         DisplayTitle("");
11146     }
11147
11148     if (gameMode == MachinePlaysWhite ||
11149         gameMode == MachinePlaysBlack ||
11150         gameMode == TwoMachinesPlay ||
11151         gameMode == EndOfGame) {
11152         i = forwardMostMove;
11153         while (i > currentMove) {
11154             SendToProgram("undo\n", &first);
11155             i--;
11156         }
11157         whiteTimeRemaining = timeRemaining[0][currentMove];
11158         blackTimeRemaining = timeRemaining[1][currentMove];
11159         DisplayBothClocks();
11160         if (whiteFlag || blackFlag) {
11161             whiteFlag = blackFlag = 0;
11162         }
11163         DisplayTitle("");
11164     }
11165
11166     gameMode = EditGame;
11167     ModeHighlight();
11168     SetGameInfo();
11169 }
11170
11171
11172 void
11173 EditPositionEvent()
11174 {
11175     if (gameMode == EditPosition) {
11176         EditGameEvent();
11177         return;
11178     }
11179
11180     EditGameEvent();
11181     if (gameMode != EditGame) return;
11182
11183     gameMode = EditPosition;
11184     ModeHighlight();
11185     SetGameInfo();
11186     if (currentMove > 0)
11187       CopyBoard(boards[0], boards[currentMove]);
11188
11189     blackPlaysFirst = !WhiteOnMove(currentMove);
11190     ResetClocks();
11191     currentMove = forwardMostMove = backwardMostMove = 0;
11192     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11193     DisplayMove(-1);
11194 }
11195
11196 void
11197 ExitAnalyzeMode()
11198 {
11199     /* [DM] icsEngineAnalyze - possible call from other functions */
11200     if (appData.icsEngineAnalyze) {
11201         appData.icsEngineAnalyze = FALSE;
11202
11203         DisplayMessage("",_("Close ICS engine analyze..."));
11204     }
11205     if (first.analysisSupport && first.analyzing) {
11206       SendToProgram("exit\n", &first);
11207       first.analyzing = FALSE;
11208     }
11209     thinkOutput[0] = NULLCHAR;
11210 }
11211
11212 void
11213 EditPositionDone(Boolean fakeRights)
11214 {
11215     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11216
11217     startedFromSetupPosition = TRUE;
11218     InitChessProgram(&first, FALSE);
11219     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11220       boards[0][EP_STATUS] = EP_NONE;
11221       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11222     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11223         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11224         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11225       } else boards[0][CASTLING][2] = NoRights;
11226     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11227         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11228         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11229       } else boards[0][CASTLING][5] = NoRights;
11230     }
11231     SendToProgram("force\n", &first);
11232     if (blackPlaysFirst) {
11233         strcpy(moveList[0], "");
11234         strcpy(parseList[0], "");
11235         currentMove = forwardMostMove = backwardMostMove = 1;
11236         CopyBoard(boards[1], boards[0]);
11237     } else {
11238         currentMove = forwardMostMove = backwardMostMove = 0;
11239     }
11240     SendBoard(&first, forwardMostMove);
11241     if (appData.debugMode) {
11242         fprintf(debugFP, "EditPosDone\n");
11243     }
11244     DisplayTitle("");
11245     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11246     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11247     gameMode = EditGame;
11248     ModeHighlight();
11249     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11250     ClearHighlights(); /* [AS] */
11251 }
11252
11253 /* Pause for `ms' milliseconds */
11254 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11255 void
11256 TimeDelay(ms)
11257      long ms;
11258 {
11259     TimeMark m1, m2;
11260
11261     GetTimeMark(&m1);
11262     do {
11263         GetTimeMark(&m2);
11264     } while (SubtractTimeMarks(&m2, &m1) < ms);
11265 }
11266
11267 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11268 void
11269 SendMultiLineToICS(buf)
11270      char *buf;
11271 {
11272     char temp[MSG_SIZ+1], *p;
11273     int len;
11274
11275     len = strlen(buf);
11276     if (len > MSG_SIZ)
11277       len = MSG_SIZ;
11278
11279     strncpy(temp, buf, len);
11280     temp[len] = 0;
11281
11282     p = temp;
11283     while (*p) {
11284         if (*p == '\n' || *p == '\r')
11285           *p = ' ';
11286         ++p;
11287     }
11288
11289     strcat(temp, "\n");
11290     SendToICS(temp);
11291     SendToPlayer(temp, strlen(temp));
11292 }
11293
11294 void
11295 SetWhiteToPlayEvent()
11296 {
11297     if (gameMode == EditPosition) {
11298         blackPlaysFirst = FALSE;
11299         DisplayBothClocks();    /* works because currentMove is 0 */
11300     } else if (gameMode == IcsExamining) {
11301         SendToICS(ics_prefix);
11302         SendToICS("tomove white\n");
11303     }
11304 }
11305
11306 void
11307 SetBlackToPlayEvent()
11308 {
11309     if (gameMode == EditPosition) {
11310         blackPlaysFirst = TRUE;
11311         currentMove = 1;        /* kludge */
11312         DisplayBothClocks();
11313         currentMove = 0;
11314     } else if (gameMode == IcsExamining) {
11315         SendToICS(ics_prefix);
11316         SendToICS("tomove black\n");
11317     }
11318 }
11319
11320 void
11321 EditPositionMenuEvent(selection, x, y)
11322      ChessSquare selection;
11323      int x, y;
11324 {
11325     char buf[MSG_SIZ];
11326     ChessSquare piece = boards[0][y][x];
11327
11328     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11329
11330     switch (selection) {
11331       case ClearBoard:
11332         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11333             SendToICS(ics_prefix);
11334             SendToICS("bsetup clear\n");
11335         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11336             SendToICS(ics_prefix);
11337             SendToICS("clearboard\n");
11338         } else {
11339             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11340                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11341                 for (y = 0; y < BOARD_HEIGHT; y++) {
11342                     if (gameMode == IcsExamining) {
11343                         if (boards[currentMove][y][x] != EmptySquare) {
11344                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11345                                     AAA + x, ONE + y);
11346                             SendToICS(buf);
11347                         }
11348                     } else {
11349                         boards[0][y][x] = p;
11350                     }
11351                 }
11352             }
11353         }
11354         if (gameMode == EditPosition) {
11355             DrawPosition(FALSE, boards[0]);
11356         }
11357         break;
11358
11359       case WhitePlay:
11360         SetWhiteToPlayEvent();
11361         break;
11362
11363       case BlackPlay:
11364         SetBlackToPlayEvent();
11365         break;
11366
11367       case EmptySquare:
11368         if (gameMode == IcsExamining) {
11369             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11370             SendToICS(buf);
11371         } else {
11372             boards[0][y][x] = EmptySquare;
11373             DrawPosition(FALSE, boards[0]);
11374         }
11375         break;
11376
11377       case PromotePiece:
11378         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11379            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11380             selection = (ChessSquare) (PROMOTED piece);
11381         } else if(piece == EmptySquare) selection = WhiteSilver;
11382         else selection = (ChessSquare)((int)piece - 1);
11383         goto defaultlabel;
11384
11385       case DemotePiece:
11386         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11387            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11388             selection = (ChessSquare) (DEMOTED piece);
11389         } else if(piece == EmptySquare) selection = BlackSilver;
11390         else selection = (ChessSquare)((int)piece + 1);
11391         goto defaultlabel;
11392
11393       case WhiteQueen:
11394       case BlackQueen:
11395         if(gameInfo.variant == VariantShatranj ||
11396            gameInfo.variant == VariantXiangqi  ||
11397            gameInfo.variant == VariantCourier    )
11398             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11399         goto defaultlabel;
11400
11401       case WhiteKing:
11402       case BlackKing:
11403         if(gameInfo.variant == VariantXiangqi)
11404             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11405         if(gameInfo.variant == VariantKnightmate)
11406             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11407       default:
11408         defaultlabel:
11409         if (gameMode == IcsExamining) {
11410             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11411                     PieceToChar(selection), AAA + x, ONE + y);
11412             SendToICS(buf);
11413         } else {
11414             boards[0][y][x] = selection;
11415             DrawPosition(FALSE, boards[0]);
11416         }
11417         break;
11418     }
11419 }
11420
11421
11422 void
11423 DropMenuEvent(selection, x, y)
11424      ChessSquare selection;
11425      int x, y;
11426 {
11427     ChessMove moveType;
11428
11429     switch (gameMode) {
11430       case IcsPlayingWhite:
11431       case MachinePlaysBlack:
11432         if (!WhiteOnMove(currentMove)) {
11433             DisplayMoveError(_("It is Black's turn"));
11434             return;
11435         }
11436         moveType = WhiteDrop;
11437         break;
11438       case IcsPlayingBlack:
11439       case MachinePlaysWhite:
11440         if (WhiteOnMove(currentMove)) {
11441             DisplayMoveError(_("It is White's turn"));
11442             return;
11443         }
11444         moveType = BlackDrop;
11445         break;
11446       case EditGame:
11447         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11448         break;
11449       default:
11450         return;
11451     }
11452
11453     if (moveType == BlackDrop && selection < BlackPawn) {
11454       selection = (ChessSquare) ((int) selection
11455                                  + (int) BlackPawn - (int) WhitePawn);
11456     }
11457     if (boards[currentMove][y][x] != EmptySquare) {
11458         DisplayMoveError(_("That square is occupied"));
11459         return;
11460     }
11461
11462     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11463 }
11464
11465 void
11466 AcceptEvent()
11467 {
11468     /* Accept a pending offer of any kind from opponent */
11469
11470     if (appData.icsActive) {
11471         SendToICS(ics_prefix);
11472         SendToICS("accept\n");
11473     } else if (cmailMsgLoaded) {
11474         if (currentMove == cmailOldMove &&
11475             commentList[cmailOldMove] != NULL &&
11476             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11477                    "Black offers a draw" : "White offers a draw")) {
11478             TruncateGame();
11479             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11480             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11481         } else {
11482             DisplayError(_("There is no pending offer on this move"), 0);
11483             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11484         }
11485     } else {
11486         /* Not used for offers from chess program */
11487     }
11488 }
11489
11490 void
11491 DeclineEvent()
11492 {
11493     /* Decline a pending offer of any kind from opponent */
11494
11495     if (appData.icsActive) {
11496         SendToICS(ics_prefix);
11497         SendToICS("decline\n");
11498     } else if (cmailMsgLoaded) {
11499         if (currentMove == cmailOldMove &&
11500             commentList[cmailOldMove] != NULL &&
11501             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11502                    "Black offers a draw" : "White offers a draw")) {
11503 #ifdef NOTDEF
11504             AppendComment(cmailOldMove, "Draw declined", TRUE);
11505             DisplayComment(cmailOldMove - 1, "Draw declined");
11506 #endif /*NOTDEF*/
11507         } else {
11508             DisplayError(_("There is no pending offer on this move"), 0);
11509         }
11510     } else {
11511         /* Not used for offers from chess program */
11512     }
11513 }
11514
11515 void
11516 RematchEvent()
11517 {
11518     /* Issue ICS rematch command */
11519     if (appData.icsActive) {
11520         SendToICS(ics_prefix);
11521         SendToICS("rematch\n");
11522     }
11523 }
11524
11525 void
11526 CallFlagEvent()
11527 {
11528     /* Call your opponent's flag (claim a win on time) */
11529     if (appData.icsActive) {
11530         SendToICS(ics_prefix);
11531         SendToICS("flag\n");
11532     } else {
11533         switch (gameMode) {
11534           default:
11535             return;
11536           case MachinePlaysWhite:
11537             if (whiteFlag) {
11538                 if (blackFlag)
11539                   GameEnds(GameIsDrawn, "Both players ran out of time",
11540                            GE_PLAYER);
11541                 else
11542                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11543             } else {
11544                 DisplayError(_("Your opponent is not out of time"), 0);
11545             }
11546             break;
11547           case MachinePlaysBlack:
11548             if (blackFlag) {
11549                 if (whiteFlag)
11550                   GameEnds(GameIsDrawn, "Both players ran out of time",
11551                            GE_PLAYER);
11552                 else
11553                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11554             } else {
11555                 DisplayError(_("Your opponent is not out of time"), 0);
11556             }
11557             break;
11558         }
11559     }
11560 }
11561
11562 void
11563 DrawEvent()
11564 {
11565     /* Offer draw or accept pending draw offer from opponent */
11566
11567     if (appData.icsActive) {
11568         /* Note: tournament rules require draw offers to be
11569            made after you make your move but before you punch
11570            your clock.  Currently ICS doesn't let you do that;
11571            instead, you immediately punch your clock after making
11572            a move, but you can offer a draw at any time. */
11573
11574         SendToICS(ics_prefix);
11575         SendToICS("draw\n");
11576     } else if (cmailMsgLoaded) {
11577         if (currentMove == cmailOldMove &&
11578             commentList[cmailOldMove] != NULL &&
11579             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11580                    "Black offers a draw" : "White offers a draw")) {
11581             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11582             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11583         } else if (currentMove == cmailOldMove + 1) {
11584             char *offer = WhiteOnMove(cmailOldMove) ?
11585               "White offers a draw" : "Black offers a draw";
11586             AppendComment(currentMove, offer, TRUE);
11587             DisplayComment(currentMove - 1, offer);
11588             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11589         } else {
11590             DisplayError(_("You must make your move before offering a draw"), 0);
11591             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11592         }
11593     } else if (first.offeredDraw) {
11594         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11595     } else {
11596         if (first.sendDrawOffers) {
11597             SendToProgram("draw\n", &first);
11598             userOfferedDraw = TRUE;
11599         }
11600     }
11601 }
11602
11603 void
11604 AdjournEvent()
11605 {
11606     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11607
11608     if (appData.icsActive) {
11609         SendToICS(ics_prefix);
11610         SendToICS("adjourn\n");
11611     } else {
11612         /* Currently GNU Chess doesn't offer or accept Adjourns */
11613     }
11614 }
11615
11616
11617 void
11618 AbortEvent()
11619 {
11620     /* Offer Abort or accept pending Abort offer from opponent */
11621
11622     if (appData.icsActive) {
11623         SendToICS(ics_prefix);
11624         SendToICS("abort\n");
11625     } else {
11626         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11627     }
11628 }
11629
11630 void
11631 ResignEvent()
11632 {
11633     /* Resign.  You can do this even if it's not your turn. */
11634
11635     if (appData.icsActive) {
11636         SendToICS(ics_prefix);
11637         SendToICS("resign\n");
11638     } else {
11639         switch (gameMode) {
11640           case MachinePlaysWhite:
11641             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11642             break;
11643           case MachinePlaysBlack:
11644             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11645             break;
11646           case EditGame:
11647             if (cmailMsgLoaded) {
11648                 TruncateGame();
11649                 if (WhiteOnMove(cmailOldMove)) {
11650                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11651                 } else {
11652                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11653                 }
11654                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11655             }
11656             break;
11657           default:
11658             break;
11659         }
11660     }
11661 }
11662
11663
11664 void
11665 StopObservingEvent()
11666 {
11667     /* Stop observing current games */
11668     SendToICS(ics_prefix);
11669     SendToICS("unobserve\n");
11670 }
11671
11672 void
11673 StopExaminingEvent()
11674 {
11675     /* Stop observing current game */
11676     SendToICS(ics_prefix);
11677     SendToICS("unexamine\n");
11678 }
11679
11680 void
11681 ForwardInner(target)
11682      int target;
11683 {
11684     int limit;
11685
11686     if (appData.debugMode)
11687         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11688                 target, currentMove, forwardMostMove);
11689
11690     if (gameMode == EditPosition)
11691       return;
11692
11693     if (gameMode == PlayFromGameFile && !pausing)
11694       PauseEvent();
11695
11696     if (gameMode == IcsExamining && pausing)
11697       limit = pauseExamForwardMostMove;
11698     else
11699       limit = forwardMostMove;
11700
11701     if (target > limit) target = limit;
11702
11703     if (target > 0 && moveList[target - 1][0]) {
11704         int fromX, fromY, toX, toY;
11705         toX = moveList[target - 1][2] - AAA;
11706         toY = moveList[target - 1][3] - ONE;
11707         if (moveList[target - 1][1] == '@') {
11708             if (appData.highlightLastMove) {
11709                 SetHighlights(-1, -1, toX, toY);
11710             }
11711         } else {
11712             fromX = moveList[target - 1][0] - AAA;
11713             fromY = moveList[target - 1][1] - ONE;
11714             if (target == currentMove + 1) {
11715                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11716             }
11717             if (appData.highlightLastMove) {
11718                 SetHighlights(fromX, fromY, toX, toY);
11719             }
11720         }
11721     }
11722     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11723         gameMode == Training || gameMode == PlayFromGameFile ||
11724         gameMode == AnalyzeFile) {
11725         while (currentMove < target) {
11726             SendMoveToProgram(currentMove++, &first);
11727         }
11728     } else {
11729         currentMove = target;
11730     }
11731
11732     if (gameMode == EditGame || gameMode == EndOfGame) {
11733         whiteTimeRemaining = timeRemaining[0][currentMove];
11734         blackTimeRemaining = timeRemaining[1][currentMove];
11735     }
11736     DisplayBothClocks();
11737     DisplayMove(currentMove - 1);
11738     DrawPosition(FALSE, boards[currentMove]);
11739     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11740     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11741         DisplayComment(currentMove - 1, commentList[currentMove]);
11742     }
11743 }
11744
11745
11746 void
11747 ForwardEvent()
11748 {
11749     if (gameMode == IcsExamining && !pausing) {
11750         SendToICS(ics_prefix);
11751         SendToICS("forward\n");
11752     } else {
11753         ForwardInner(currentMove + 1);
11754     }
11755 }
11756
11757 void
11758 ToEndEvent()
11759 {
11760     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11761         /* to optimze, we temporarily turn off analysis mode while we feed
11762          * the remaining moves to the engine. Otherwise we get analysis output
11763          * after each move.
11764          */
11765         if (first.analysisSupport) {
11766           SendToProgram("exit\nforce\n", &first);
11767           first.analyzing = FALSE;
11768         }
11769     }
11770
11771     if (gameMode == IcsExamining && !pausing) {
11772         SendToICS(ics_prefix);
11773         SendToICS("forward 999999\n");
11774     } else {
11775         ForwardInner(forwardMostMove);
11776     }
11777
11778     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11779         /* we have fed all the moves, so reactivate analysis mode */
11780         SendToProgram("analyze\n", &first);
11781         first.analyzing = TRUE;
11782         /*first.maybeThinking = TRUE;*/
11783         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11784     }
11785 }
11786
11787 void
11788 BackwardInner(target)
11789      int target;
11790 {
11791     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11792
11793     if (appData.debugMode)
11794         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11795                 target, currentMove, forwardMostMove);
11796
11797     if (gameMode == EditPosition) return;
11798     if (currentMove <= backwardMostMove) {
11799         ClearHighlights();
11800         DrawPosition(full_redraw, boards[currentMove]);
11801         return;
11802     }
11803     if (gameMode == PlayFromGameFile && !pausing)
11804       PauseEvent();
11805
11806     if (moveList[target][0]) {
11807         int fromX, fromY, toX, toY;
11808         toX = moveList[target][2] - AAA;
11809         toY = moveList[target][3] - ONE;
11810         if (moveList[target][1] == '@') {
11811             if (appData.highlightLastMove) {
11812                 SetHighlights(-1, -1, toX, toY);
11813             }
11814         } else {
11815             fromX = moveList[target][0] - AAA;
11816             fromY = moveList[target][1] - ONE;
11817             if (target == currentMove - 1) {
11818                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11819             }
11820             if (appData.highlightLastMove) {
11821                 SetHighlights(fromX, fromY, toX, toY);
11822             }
11823         }
11824     }
11825     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11826         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11827         while (currentMove > target) {
11828             SendToProgram("undo\n", &first);
11829             currentMove--;
11830         }
11831     } else {
11832         currentMove = target;
11833     }
11834
11835     if (gameMode == EditGame || gameMode == EndOfGame) {
11836         whiteTimeRemaining = timeRemaining[0][currentMove];
11837         blackTimeRemaining = timeRemaining[1][currentMove];
11838     }
11839     DisplayBothClocks();
11840     DisplayMove(currentMove - 1);
11841     DrawPosition(full_redraw, boards[currentMove]);
11842     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11843     // [HGM] PV info: routine tests if comment empty
11844     DisplayComment(currentMove - 1, commentList[currentMove]);
11845 }
11846
11847 void
11848 BackwardEvent()
11849 {
11850     if (gameMode == IcsExamining && !pausing) {
11851         SendToICS(ics_prefix);
11852         SendToICS("backward\n");
11853     } else {
11854         BackwardInner(currentMove - 1);
11855     }
11856 }
11857
11858 void
11859 ToStartEvent()
11860 {
11861     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11862         /* to optimize, we temporarily turn off analysis mode while we undo
11863          * all the moves. Otherwise we get analysis output after each undo.
11864          */
11865         if (first.analysisSupport) {
11866           SendToProgram("exit\nforce\n", &first);
11867           first.analyzing = FALSE;
11868         }
11869     }
11870
11871     if (gameMode == IcsExamining && !pausing) {
11872         SendToICS(ics_prefix);
11873         SendToICS("backward 999999\n");
11874     } else {
11875         BackwardInner(backwardMostMove);
11876     }
11877
11878     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11879         /* we have fed all the moves, so reactivate analysis mode */
11880         SendToProgram("analyze\n", &first);
11881         first.analyzing = TRUE;
11882         /*first.maybeThinking = TRUE;*/
11883         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11884     }
11885 }
11886
11887 void
11888 ToNrEvent(int to)
11889 {
11890   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11891   if (to >= forwardMostMove) to = forwardMostMove;
11892   if (to <= backwardMostMove) to = backwardMostMove;
11893   if (to < currentMove) {
11894     BackwardInner(to);
11895   } else {
11896     ForwardInner(to);
11897   }
11898 }
11899
11900 void
11901 RevertEvent()
11902 {
11903     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11904         return;
11905     }
11906     if (gameMode != IcsExamining) {
11907         DisplayError(_("You are not examining a game"), 0);
11908         return;
11909     }
11910     if (pausing) {
11911         DisplayError(_("You can't revert while pausing"), 0);
11912         return;
11913     }
11914     SendToICS(ics_prefix);
11915     SendToICS("revert\n");
11916 }
11917
11918 void
11919 RetractMoveEvent()
11920 {
11921     switch (gameMode) {
11922       case MachinePlaysWhite:
11923       case MachinePlaysBlack:
11924         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11925             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11926             return;
11927         }
11928         if (forwardMostMove < 2) return;
11929         currentMove = forwardMostMove = forwardMostMove - 2;
11930         whiteTimeRemaining = timeRemaining[0][currentMove];
11931         blackTimeRemaining = timeRemaining[1][currentMove];
11932         DisplayBothClocks();
11933         DisplayMove(currentMove - 1);
11934         ClearHighlights();/*!! could figure this out*/
11935         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11936         SendToProgram("remove\n", &first);
11937         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11938         break;
11939
11940       case BeginningOfGame:
11941       default:
11942         break;
11943
11944       case IcsPlayingWhite:
11945       case IcsPlayingBlack:
11946         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11947             SendToICS(ics_prefix);
11948             SendToICS("takeback 2\n");
11949         } else {
11950             SendToICS(ics_prefix);
11951             SendToICS("takeback 1\n");
11952         }
11953         break;
11954     }
11955 }
11956
11957 void
11958 MoveNowEvent()
11959 {
11960     ChessProgramState *cps;
11961
11962     switch (gameMode) {
11963       case MachinePlaysWhite:
11964         if (!WhiteOnMove(forwardMostMove)) {
11965             DisplayError(_("It is your turn"), 0);
11966             return;
11967         }
11968         cps = &first;
11969         break;
11970       case MachinePlaysBlack:
11971         if (WhiteOnMove(forwardMostMove)) {
11972             DisplayError(_("It is your turn"), 0);
11973             return;
11974         }
11975         cps = &first;
11976         break;
11977       case TwoMachinesPlay:
11978         if (WhiteOnMove(forwardMostMove) ==
11979             (first.twoMachinesColor[0] == 'w')) {
11980             cps = &first;
11981         } else {
11982             cps = &second;
11983         }
11984         break;
11985       case BeginningOfGame:
11986       default:
11987         return;
11988     }
11989     SendToProgram("?\n", cps);
11990 }
11991
11992 void
11993 TruncateGameEvent()
11994 {
11995     EditGameEvent();
11996     if (gameMode != EditGame) return;
11997     TruncateGame();
11998 }
11999
12000 void
12001 TruncateGame()
12002 {
12003     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12004     if (forwardMostMove > currentMove) {
12005         if (gameInfo.resultDetails != NULL) {
12006             free(gameInfo.resultDetails);
12007             gameInfo.resultDetails = NULL;
12008             gameInfo.result = GameUnfinished;
12009         }
12010         forwardMostMove = currentMove;
12011         HistorySet(parseList, backwardMostMove, forwardMostMove,
12012                    currentMove-1);
12013     }
12014 }
12015
12016 void
12017 HintEvent()
12018 {
12019     if (appData.noChessProgram) return;
12020     switch (gameMode) {
12021       case MachinePlaysWhite:
12022         if (WhiteOnMove(forwardMostMove)) {
12023             DisplayError(_("Wait until your turn"), 0);
12024             return;
12025         }
12026         break;
12027       case BeginningOfGame:
12028       case MachinePlaysBlack:
12029         if (!WhiteOnMove(forwardMostMove)) {
12030             DisplayError(_("Wait until your turn"), 0);
12031             return;
12032         }
12033         break;
12034       default:
12035         DisplayError(_("No hint available"), 0);
12036         return;
12037     }
12038     SendToProgram("hint\n", &first);
12039     hintRequested = TRUE;
12040 }
12041
12042 void
12043 BookEvent()
12044 {
12045     if (appData.noChessProgram) return;
12046     switch (gameMode) {
12047       case MachinePlaysWhite:
12048         if (WhiteOnMove(forwardMostMove)) {
12049             DisplayError(_("Wait until your turn"), 0);
12050             return;
12051         }
12052         break;
12053       case BeginningOfGame:
12054       case MachinePlaysBlack:
12055         if (!WhiteOnMove(forwardMostMove)) {
12056             DisplayError(_("Wait until your turn"), 0);
12057             return;
12058         }
12059         break;
12060       case EditPosition:
12061         EditPositionDone(TRUE);
12062         break;
12063       case TwoMachinesPlay:
12064         return;
12065       default:
12066         break;
12067     }
12068     SendToProgram("bk\n", &first);
12069     bookOutput[0] = NULLCHAR;
12070     bookRequested = TRUE;
12071 }
12072
12073 void
12074 AboutGameEvent()
12075 {
12076     char *tags = PGNTags(&gameInfo);
12077     TagsPopUp(tags, CmailMsg());
12078     free(tags);
12079 }
12080
12081 /* end button procedures */
12082
12083 void
12084 PrintPosition(fp, move)
12085      FILE *fp;
12086      int move;
12087 {
12088     int i, j;
12089
12090     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12091         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12092             char c = PieceToChar(boards[move][i][j]);
12093             fputc(c == 'x' ? '.' : c, fp);
12094             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12095         }
12096     }
12097     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12098       fprintf(fp, "white to play\n");
12099     else
12100       fprintf(fp, "black to play\n");
12101 }
12102
12103 void
12104 PrintOpponents(fp)
12105      FILE *fp;
12106 {
12107     if (gameInfo.white != NULL) {
12108         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12109     } else {
12110         fprintf(fp, "\n");
12111     }
12112 }
12113
12114 /* Find last component of program's own name, using some heuristics */
12115 void
12116 TidyProgramName(prog, host, buf)
12117      char *prog, *host, buf[MSG_SIZ];
12118 {
12119     char *p, *q;
12120     int local = (strcmp(host, "localhost") == 0);
12121     while (!local && (p = strchr(prog, ';')) != NULL) {
12122         p++;
12123         while (*p == ' ') p++;
12124         prog = p;
12125     }
12126     if (*prog == '"' || *prog == '\'') {
12127         q = strchr(prog + 1, *prog);
12128     } else {
12129         q = strchr(prog, ' ');
12130     }
12131     if (q == NULL) q = prog + strlen(prog);
12132     p = q;
12133     while (p >= prog && *p != '/' && *p != '\\') p--;
12134     p++;
12135     if(p == prog && *p == '"') p++;
12136     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12137     memcpy(buf, p, q - p);
12138     buf[q - p] = NULLCHAR;
12139     if (!local) {
12140         strcat(buf, "@");
12141         strcat(buf, host);
12142     }
12143 }
12144
12145 char *
12146 TimeControlTagValue()
12147 {
12148     char buf[MSG_SIZ];
12149     if (!appData.clockMode) {
12150         strcpy(buf, "-");
12151     } else if (movesPerSession > 0) {
12152         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12153     } else if (timeIncrement == 0) {
12154         sprintf(buf, "%ld", timeControl/1000);
12155     } else {
12156         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12157     }
12158     return StrSave(buf);
12159 }
12160
12161 void
12162 SetGameInfo()
12163 {
12164     /* This routine is used only for certain modes */
12165     VariantClass v = gameInfo.variant;
12166     ChessMove r = GameUnfinished;
12167     char *p = NULL;
12168
12169     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12170         r = gameInfo.result; 
12171         p = gameInfo.resultDetails; 
12172         gameInfo.resultDetails = NULL;
12173     }
12174     ClearGameInfo(&gameInfo);
12175     gameInfo.variant = v;
12176
12177     switch (gameMode) {
12178       case MachinePlaysWhite:
12179         gameInfo.event = StrSave( appData.pgnEventHeader );
12180         gameInfo.site = StrSave(HostName());
12181         gameInfo.date = PGNDate();
12182         gameInfo.round = StrSave("-");
12183         gameInfo.white = StrSave(first.tidy);
12184         gameInfo.black = StrSave(UserName());
12185         gameInfo.timeControl = TimeControlTagValue();
12186         break;
12187
12188       case MachinePlaysBlack:
12189         gameInfo.event = StrSave( appData.pgnEventHeader );
12190         gameInfo.site = StrSave(HostName());
12191         gameInfo.date = PGNDate();
12192         gameInfo.round = StrSave("-");
12193         gameInfo.white = StrSave(UserName());
12194         gameInfo.black = StrSave(first.tidy);
12195         gameInfo.timeControl = TimeControlTagValue();
12196         break;
12197
12198       case TwoMachinesPlay:
12199         gameInfo.event = StrSave( appData.pgnEventHeader );
12200         gameInfo.site = StrSave(HostName());
12201         gameInfo.date = PGNDate();
12202         if (matchGame > 0) {
12203             char buf[MSG_SIZ];
12204             sprintf(buf, "%d", matchGame);
12205             gameInfo.round = StrSave(buf);
12206         } else {
12207             gameInfo.round = StrSave("-");
12208         }
12209         if (first.twoMachinesColor[0] == 'w') {
12210             gameInfo.white = StrSave(first.tidy);
12211             gameInfo.black = StrSave(second.tidy);
12212         } else {
12213             gameInfo.white = StrSave(second.tidy);
12214             gameInfo.black = StrSave(first.tidy);
12215         }
12216         gameInfo.timeControl = TimeControlTagValue();
12217         break;
12218
12219       case EditGame:
12220         gameInfo.event = StrSave("Edited game");
12221         gameInfo.site = StrSave(HostName());
12222         gameInfo.date = PGNDate();
12223         gameInfo.round = StrSave("-");
12224         gameInfo.white = StrSave("-");
12225         gameInfo.black = StrSave("-");
12226         gameInfo.result = r;
12227         gameInfo.resultDetails = p;
12228         break;
12229
12230       case EditPosition:
12231         gameInfo.event = StrSave("Edited position");
12232         gameInfo.site = StrSave(HostName());
12233         gameInfo.date = PGNDate();
12234         gameInfo.round = StrSave("-");
12235         gameInfo.white = StrSave("-");
12236         gameInfo.black = StrSave("-");
12237         break;
12238
12239       case IcsPlayingWhite:
12240       case IcsPlayingBlack:
12241       case IcsObserving:
12242       case IcsExamining:
12243         break;
12244
12245       case PlayFromGameFile:
12246         gameInfo.event = StrSave("Game from non-PGN file");
12247         gameInfo.site = StrSave(HostName());
12248         gameInfo.date = PGNDate();
12249         gameInfo.round = StrSave("-");
12250         gameInfo.white = StrSave("?");
12251         gameInfo.black = StrSave("?");
12252         break;
12253
12254       default:
12255         break;
12256     }
12257 }
12258
12259 void
12260 ReplaceComment(index, text)
12261      int index;
12262      char *text;
12263 {
12264     int len;
12265
12266     while (*text == '\n') text++;
12267     len = strlen(text);
12268     while (len > 0 && text[len - 1] == '\n') len--;
12269
12270     if (commentList[index] != NULL)
12271       free(commentList[index]);
12272
12273     if (len == 0) {
12274         commentList[index] = NULL;
12275         return;
12276     }
12277   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12278       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12279       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12280     commentList[index] = (char *) malloc(len + 2);
12281     strncpy(commentList[index], text, len);
12282     commentList[index][len] = '\n';
12283     commentList[index][len + 1] = NULLCHAR;
12284   } else { 
12285     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12286     char *p;
12287     commentList[index] = (char *) malloc(len + 6);
12288     strcpy(commentList[index], "{\n");
12289     strncpy(commentList[index]+2, text, len);
12290     commentList[index][len+2] = NULLCHAR;
12291     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12292     strcat(commentList[index], "\n}\n");
12293   }
12294 }
12295
12296 void
12297 CrushCRs(text)
12298      char *text;
12299 {
12300   char *p = text;
12301   char *q = text;
12302   char ch;
12303
12304   do {
12305     ch = *p++;
12306     if (ch == '\r') continue;
12307     *q++ = ch;
12308   } while (ch != '\0');
12309 }
12310
12311 void
12312 AppendComment(index, text, addBraces)
12313      int index;
12314      char *text;
12315      Boolean addBraces; // [HGM] braces: tells if we should add {}
12316 {
12317     int oldlen, len;
12318     char *old;
12319
12320 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12321     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12322
12323     CrushCRs(text);
12324     while (*text == '\n') text++;
12325     len = strlen(text);
12326     while (len > 0 && text[len - 1] == '\n') len--;
12327
12328     if (len == 0) return;
12329
12330     if (commentList[index] != NULL) {
12331         old = commentList[index];
12332         oldlen = strlen(old);
12333         while(commentList[index][oldlen-1] ==  '\n')
12334           commentList[index][--oldlen] = NULLCHAR;
12335         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12336         strcpy(commentList[index], old);
12337         free(old);
12338         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12339         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12340           if(addBraces) addBraces = FALSE; else { text++; len--; }
12341           while (*text == '\n') { text++; len--; }
12342           commentList[index][--oldlen] = NULLCHAR;
12343       }
12344         if(addBraces) strcat(commentList[index], "\n{\n");
12345         else          strcat(commentList[index], "\n");
12346         strcat(commentList[index], text);
12347         if(addBraces) strcat(commentList[index], "\n}\n");
12348         else          strcat(commentList[index], "\n");
12349     } else {
12350         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12351         if(addBraces)
12352              strcpy(commentList[index], "{\n");
12353         else commentList[index][0] = NULLCHAR;
12354         strcat(commentList[index], text);
12355         strcat(commentList[index], "\n");
12356         if(addBraces) strcat(commentList[index], "}\n");
12357     }
12358 }
12359
12360 static char * FindStr( char * text, char * sub_text )
12361 {
12362     char * result = strstr( text, sub_text );
12363
12364     if( result != NULL ) {
12365         result += strlen( sub_text );
12366     }
12367
12368     return result;
12369 }
12370
12371 /* [AS] Try to extract PV info from PGN comment */
12372 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12373 char *GetInfoFromComment( int index, char * text )
12374 {
12375     char * sep = text;
12376
12377     if( text != NULL && index > 0 ) {
12378         int score = 0;
12379         int depth = 0;
12380         int time = -1, sec = 0, deci;
12381         char * s_eval = FindStr( text, "[%eval " );
12382         char * s_emt = FindStr( text, "[%emt " );
12383
12384         if( s_eval != NULL || s_emt != NULL ) {
12385             /* New style */
12386             char delim;
12387
12388             if( s_eval != NULL ) {
12389                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12390                     return text;
12391                 }
12392
12393                 if( delim != ']' ) {
12394                     return text;
12395                 }
12396             }
12397
12398             if( s_emt != NULL ) {
12399             }
12400                 return text;
12401         }
12402         else {
12403             /* We expect something like: [+|-]nnn.nn/dd */
12404             int score_lo = 0;
12405
12406             if(*text != '{') return text; // [HGM] braces: must be normal comment
12407
12408             sep = strchr( text, '/' );
12409             if( sep == NULL || sep < (text+4) ) {
12410                 return text;
12411             }
12412
12413             time = -1; sec = -1; deci = -1;
12414             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12415                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12416                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12417                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12418                 return text;
12419             }
12420
12421             if( score_lo < 0 || score_lo >= 100 ) {
12422                 return text;
12423             }
12424
12425             if(sec >= 0) time = 600*time + 10*sec; else
12426             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12427
12428             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12429
12430             /* [HGM] PV time: now locate end of PV info */
12431             while( *++sep >= '0' && *sep <= '9'); // strip depth
12432             if(time >= 0)
12433             while( *++sep >= '0' && *sep <= '9'); // strip time
12434             if(sec >= 0)
12435             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12436             if(deci >= 0)
12437             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12438             while(*sep == ' ') sep++;
12439         }
12440
12441         if( depth <= 0 ) {
12442             return text;
12443         }
12444
12445         if( time < 0 ) {
12446             time = -1;
12447         }
12448
12449         pvInfoList[index-1].depth = depth;
12450         pvInfoList[index-1].score = score;
12451         pvInfoList[index-1].time  = 10*time; // centi-sec
12452         if(*sep == '}') *sep = 0; else *--sep = '{';
12453     }
12454     return sep;
12455 }
12456
12457 void
12458 SendToProgram(message, cps)
12459      char *message;
12460      ChessProgramState *cps;
12461 {
12462     int count, outCount, error;
12463     char buf[MSG_SIZ];
12464
12465     if (cps->pr == NULL) return;
12466     Attention(cps);
12467
12468     if (appData.debugMode) {
12469         TimeMark now;
12470         GetTimeMark(&now);
12471         fprintf(debugFP, "%ld >%-6s: %s",
12472                 SubtractTimeMarks(&now, &programStartTime),
12473                 cps->which, message);
12474     }
12475
12476     count = strlen(message);
12477     outCount = OutputToProcess(cps->pr, message, count, &error);
12478     if (outCount < count && !exiting
12479                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12480         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12481         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12482             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12483                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12484                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12485             } else {
12486                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12487             }
12488             gameInfo.resultDetails = StrSave(buf);
12489         }
12490         DisplayFatalError(buf, error, 1);
12491     }
12492 }
12493
12494 void
12495 ReceiveFromProgram(isr, closure, message, count, error)
12496      InputSourceRef isr;
12497      VOIDSTAR closure;
12498      char *message;
12499      int count;
12500      int error;
12501 {
12502     char *end_str;
12503     char buf[MSG_SIZ];
12504     ChessProgramState *cps = (ChessProgramState *)closure;
12505
12506     if (isr != cps->isr) return; /* Killed intentionally */
12507     if (count <= 0) {
12508         if (count == 0) {
12509             sprintf(buf,
12510                     _("Error: %s chess program (%s) exited unexpectedly"),
12511                     cps->which, cps->program);
12512         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12513                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12514                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12515                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12516                 } else {
12517                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12518                 }
12519                 gameInfo.resultDetails = StrSave(buf);
12520             }
12521             RemoveInputSource(cps->isr);
12522             DisplayFatalError(buf, 0, 1);
12523         } else {
12524             sprintf(buf,
12525                     _("Error reading from %s chess program (%s)"),
12526                     cps->which, cps->program);
12527             RemoveInputSource(cps->isr);
12528
12529             /* [AS] Program is misbehaving badly... kill it */
12530             if( count == -2 ) {
12531                 DestroyChildProcess( cps->pr, 9 );
12532                 cps->pr = NoProc;
12533             }
12534
12535             DisplayFatalError(buf, error, 1);
12536         }
12537         return;
12538     }
12539
12540     if ((end_str = strchr(message, '\r')) != NULL)
12541       *end_str = NULLCHAR;
12542     if ((end_str = strchr(message, '\n')) != NULL)
12543       *end_str = NULLCHAR;
12544
12545     if (appData.debugMode) {
12546         TimeMark now; int print = 1;
12547         char *quote = ""; char c; int i;
12548
12549         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12550                 char start = message[0];
12551                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12552                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12553                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12554                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12555                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12556                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12557                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12558                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12559                         { quote = "# "; print = (appData.engineComments == 2); }
12560                 message[0] = start; // restore original message
12561         }
12562         if(print) {
12563                 GetTimeMark(&now);
12564                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12565                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12566                         quote,
12567                         message);
12568         }
12569     }
12570
12571     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12572     if (appData.icsEngineAnalyze) {
12573         if (strstr(message, "whisper") != NULL ||
12574              strstr(message, "kibitz") != NULL ||
12575             strstr(message, "tellics") != NULL) return;
12576     }
12577
12578     HandleMachineMove(message, cps);
12579 }
12580
12581
12582 void
12583 SendTimeControl(cps, mps, tc, inc, sd, st)
12584      ChessProgramState *cps;
12585      int mps, inc, sd, st;
12586      long tc;
12587 {
12588     char buf[MSG_SIZ];
12589     int seconds;
12590
12591     if( timeControl_2 > 0 ) {
12592         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12593             tc = timeControl_2;
12594         }
12595     }
12596     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12597     inc /= cps->timeOdds;
12598     st  /= cps->timeOdds;
12599
12600     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12601
12602     if (st > 0) {
12603       /* Set exact time per move, normally using st command */
12604       if (cps->stKludge) {
12605         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12606         seconds = st % 60;
12607         if (seconds == 0) {
12608           sprintf(buf, "level 1 %d\n", st/60);
12609         } else {
12610           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12611         }
12612       } else {
12613         sprintf(buf, "st %d\n", st);
12614       }
12615     } else {
12616       /* Set conventional or incremental time control, using level command */
12617       if (seconds == 0) {
12618         /* Note old gnuchess bug -- minutes:seconds used to not work.
12619            Fixed in later versions, but still avoid :seconds
12620            when seconds is 0. */
12621         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12622       } else {
12623         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12624                 seconds, inc/1000);
12625       }
12626     }
12627     SendToProgram(buf, cps);
12628
12629     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12630     /* Orthogonally, limit search to given depth */
12631     if (sd > 0) {
12632       if (cps->sdKludge) {
12633         sprintf(buf, "depth\n%d\n", sd);
12634       } else {
12635         sprintf(buf, "sd %d\n", sd);
12636       }
12637       SendToProgram(buf, cps);
12638     }
12639
12640     if(cps->nps > 0) { /* [HGM] nps */
12641         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12642         else {
12643                 sprintf(buf, "nps %d\n", cps->nps);
12644               SendToProgram(buf, cps);
12645         }
12646     }
12647 }
12648
12649 ChessProgramState *WhitePlayer()
12650 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12651 {
12652     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12653        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12654         return &second;
12655     return &first;
12656 }
12657
12658 void
12659 SendTimeRemaining(cps, machineWhite)
12660      ChessProgramState *cps;
12661      int /*boolean*/ machineWhite;
12662 {
12663     char message[MSG_SIZ];
12664     long time, otime;
12665
12666     /* Note: this routine must be called when the clocks are stopped
12667        or when they have *just* been set or switched; otherwise
12668        it will be off by the time since the current tick started.
12669     */
12670     if (machineWhite) {
12671         time = whiteTimeRemaining / 10;
12672         otime = blackTimeRemaining / 10;
12673     } else {
12674         time = blackTimeRemaining / 10;
12675         otime = whiteTimeRemaining / 10;
12676     }
12677     /* [HGM] translate opponent's time by time-odds factor */
12678     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12679     if (appData.debugMode) {
12680         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12681     }
12682
12683     if (time <= 0) time = 1;
12684     if (otime <= 0) otime = 1;
12685
12686     sprintf(message, "time %ld\n", time);
12687     SendToProgram(message, cps);
12688
12689     sprintf(message, "otim %ld\n", otime);
12690     SendToProgram(message, cps);
12691 }
12692
12693 int
12694 BoolFeature(p, name, loc, cps)
12695      char **p;
12696      char *name;
12697      int *loc;
12698      ChessProgramState *cps;
12699 {
12700   char buf[MSG_SIZ];
12701   int len = strlen(name);
12702   int val;
12703   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12704     (*p) += len + 1;
12705     sscanf(*p, "%d", &val);
12706     *loc = (val != 0);
12707     while (**p && **p != ' ') (*p)++;
12708     sprintf(buf, "accepted %s\n", name);
12709     SendToProgram(buf, cps);
12710     return TRUE;
12711   }
12712   return FALSE;
12713 }
12714
12715 int
12716 IntFeature(p, name, loc, cps)
12717      char **p;
12718      char *name;
12719      int *loc;
12720      ChessProgramState *cps;
12721 {
12722   char buf[MSG_SIZ];
12723   int len = strlen(name);
12724   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12725     (*p) += len + 1;
12726     sscanf(*p, "%d", loc);
12727     while (**p && **p != ' ') (*p)++;
12728     sprintf(buf, "accepted %s\n", name);
12729     SendToProgram(buf, cps);
12730     return TRUE;
12731   }
12732   return FALSE;
12733 }
12734
12735 int
12736 StringFeature(p, name, loc, cps)
12737      char **p;
12738      char *name;
12739      char loc[];
12740      ChessProgramState *cps;
12741 {
12742   char buf[MSG_SIZ];
12743   int len = strlen(name);
12744   if (strncmp((*p), name, len) == 0
12745       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12746     (*p) += len + 2;
12747     sscanf(*p, "%[^\"]", loc);
12748     while (**p && **p != '\"') (*p)++;
12749     if (**p == '\"') (*p)++;
12750     sprintf(buf, "accepted %s\n", name);
12751     SendToProgram(buf, cps);
12752     return TRUE;
12753   }
12754   return FALSE;
12755 }
12756
12757 int
12758 ParseOption(Option *opt, ChessProgramState *cps)
12759 // [HGM] options: process the string that defines an engine option, and determine
12760 // name, type, default value, and allowed value range
12761 {
12762         char *p, *q, buf[MSG_SIZ];
12763         int n, min = (-1)<<31, max = 1<<31, def;
12764
12765         if(p = strstr(opt->name, " -spin ")) {
12766             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12767             if(max < min) max = min; // enforce consistency
12768             if(def < min) def = min;
12769             if(def > max) def = max;
12770             opt->value = def;
12771             opt->min = min;
12772             opt->max = max;
12773             opt->type = Spin;
12774         } else if((p = strstr(opt->name, " -slider "))) {
12775             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12776             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12777             if(max < min) max = min; // enforce consistency
12778             if(def < min) def = min;
12779             if(def > max) def = max;
12780             opt->value = def;
12781             opt->min = min;
12782             opt->max = max;
12783             opt->type = Spin; // Slider;
12784         } else if((p = strstr(opt->name, " -string "))) {
12785             opt->textValue = p+9;
12786             opt->type = TextBox;
12787         } else if((p = strstr(opt->name, " -file "))) {
12788             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12789             opt->textValue = p+7;
12790             opt->type = TextBox; // FileName;
12791         } else if((p = strstr(opt->name, " -path "))) {
12792             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12793             opt->textValue = p+7;
12794             opt->type = TextBox; // PathName;
12795         } else if(p = strstr(opt->name, " -check ")) {
12796             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12797             opt->value = (def != 0);
12798             opt->type = CheckBox;
12799         } else if(p = strstr(opt->name, " -combo ")) {
12800             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12801             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12802             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12803             opt->value = n = 0;
12804             while(q = StrStr(q, " /// ")) {
12805                 n++; *q = 0;    // count choices, and null-terminate each of them
12806                 q += 5;
12807                 if(*q == '*') { // remember default, which is marked with * prefix
12808                     q++;
12809                     opt->value = n;
12810                 }
12811                 cps->comboList[cps->comboCnt++] = q;
12812             }
12813             cps->comboList[cps->comboCnt++] = NULL;
12814             opt->max = n + 1;
12815             opt->type = ComboBox;
12816         } else if(p = strstr(opt->name, " -button")) {
12817             opt->type = Button;
12818         } else if(p = strstr(opt->name, " -save")) {
12819             opt->type = SaveButton;
12820         } else return FALSE;
12821         *p = 0; // terminate option name
12822         // now look if the command-line options define a setting for this engine option.
12823         if(cps->optionSettings && cps->optionSettings[0])
12824             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12825         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12826                 sprintf(buf, "option %s", p);
12827                 if(p = strstr(buf, ",")) *p = 0;
12828                 strcat(buf, "\n");
12829                 SendToProgram(buf, cps);
12830         }
12831         return TRUE;
12832 }
12833
12834 void
12835 FeatureDone(cps, val)
12836      ChessProgramState* cps;
12837      int val;
12838 {
12839   DelayedEventCallback cb = GetDelayedEvent();
12840   if ((cb == InitBackEnd3 && cps == &first) ||
12841       (cb == TwoMachinesEventIfReady && cps == &second)) {
12842     CancelDelayedEvent();
12843     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12844   }
12845   cps->initDone = val;
12846 }
12847
12848 /* Parse feature command from engine */
12849 void
12850 ParseFeatures(args, cps)
12851      char* args;
12852      ChessProgramState *cps;
12853 {
12854   char *p = args;
12855   char *q;
12856   int val;
12857   char buf[MSG_SIZ];
12858
12859   for (;;) {
12860     while (*p == ' ') p++;
12861     if (*p == NULLCHAR) return;
12862
12863     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12864     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12865     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12866     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12867     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12868     if (BoolFeature(&p, "reuse", &val, cps)) {
12869       /* Engine can disable reuse, but can't enable it if user said no */
12870       if (!val) cps->reuse = FALSE;
12871       continue;
12872     }
12873     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12874     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12875       if (gameMode == TwoMachinesPlay) {
12876         DisplayTwoMachinesTitle();
12877       } else {
12878         DisplayTitle("");
12879       }
12880       continue;
12881     }
12882     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12883     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12884     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12885     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12886     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12887     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12888     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12889     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12890     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12891     if (IntFeature(&p, "done", &val, cps)) {
12892       FeatureDone(cps, val);
12893       continue;
12894     }
12895     /* Added by Tord: */
12896     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12897     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12898     /* End of additions by Tord */
12899
12900     /* [HGM] added features: */
12901     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12902     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12903     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12904     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12905     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12906     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12907     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12908         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12909             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12910             SendToProgram(buf, cps);
12911             continue;
12912         }
12913         if(cps->nrOptions >= MAX_OPTIONS) {
12914             cps->nrOptions--;
12915             sprintf(buf, "%s engine has too many options\n", cps->which);
12916             DisplayError(buf, 0);
12917         }
12918         continue;
12919     }
12920     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12921     /* End of additions by HGM */
12922
12923     /* unknown feature: complain and skip */
12924     q = p;
12925     while (*q && *q != '=') q++;
12926     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12927     SendToProgram(buf, cps);
12928     p = q;
12929     if (*p == '=') {
12930       p++;
12931       if (*p == '\"') {
12932         p++;
12933         while (*p && *p != '\"') p++;
12934         if (*p == '\"') p++;
12935       } else {
12936         while (*p && *p != ' ') p++;
12937       }
12938     }
12939   }
12940
12941 }
12942
12943 void
12944 PeriodicUpdatesEvent(newState)
12945      int newState;
12946 {
12947     if (newState == appData.periodicUpdates)
12948       return;
12949
12950     appData.periodicUpdates=newState;
12951
12952     /* Display type changes, so update it now */
12953 //    DisplayAnalysis();
12954
12955     /* Get the ball rolling again... */
12956     if (newState) {
12957         AnalysisPeriodicEvent(1);
12958         StartAnalysisClock();
12959     }
12960 }
12961
12962 void
12963 PonderNextMoveEvent(newState)
12964      int newState;
12965 {
12966     if (newState == appData.ponderNextMove) return;
12967     if (gameMode == EditPosition) EditPositionDone(TRUE);
12968     if (newState) {
12969         SendToProgram("hard\n", &first);
12970         if (gameMode == TwoMachinesPlay) {
12971             SendToProgram("hard\n", &second);
12972         }
12973     } else {
12974         SendToProgram("easy\n", &first);
12975         thinkOutput[0] = NULLCHAR;
12976         if (gameMode == TwoMachinesPlay) {
12977             SendToProgram("easy\n", &second);
12978         }
12979     }
12980     appData.ponderNextMove = newState;
12981 }
12982
12983 void
12984 NewSettingEvent(option, command, value)
12985      char *command;
12986      int option, value;
12987 {
12988     char buf[MSG_SIZ];
12989
12990     if (gameMode == EditPosition) EditPositionDone(TRUE);
12991     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12992     SendToProgram(buf, &first);
12993     if (gameMode == TwoMachinesPlay) {
12994         SendToProgram(buf, &second);
12995     }
12996 }
12997
12998 void
12999 ShowThinkingEvent()
13000 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13001 {
13002     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13003     int newState = appData.showThinking
13004         // [HGM] thinking: other features now need thinking output as well
13005         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13006
13007     if (oldState == newState) return;
13008     oldState = newState;
13009     if (gameMode == EditPosition) EditPositionDone(TRUE);
13010     if (oldState) {
13011         SendToProgram("post\n", &first);
13012         if (gameMode == TwoMachinesPlay) {
13013             SendToProgram("post\n", &second);
13014         }
13015     } else {
13016         SendToProgram("nopost\n", &first);
13017         thinkOutput[0] = NULLCHAR;
13018         if (gameMode == TwoMachinesPlay) {
13019             SendToProgram("nopost\n", &second);
13020         }
13021     }
13022 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13023 }
13024
13025 void
13026 AskQuestionEvent(title, question, replyPrefix, which)
13027      char *title; char *question; char *replyPrefix; char *which;
13028 {
13029   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13030   if (pr == NoProc) return;
13031   AskQuestion(title, question, replyPrefix, pr);
13032 }
13033
13034 void
13035 DisplayMove(moveNumber)
13036      int moveNumber;
13037 {
13038     char message[MSG_SIZ];
13039     char res[MSG_SIZ];
13040     char cpThinkOutput[MSG_SIZ];
13041
13042     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13043
13044     if (moveNumber == forwardMostMove - 1 ||
13045         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13046
13047         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13048
13049         if (strchr(cpThinkOutput, '\n')) {
13050             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13051         }
13052     } else {
13053         *cpThinkOutput = NULLCHAR;
13054     }
13055
13056     /* [AS] Hide thinking from human user */
13057     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13058         *cpThinkOutput = NULLCHAR;
13059         if( thinkOutput[0] != NULLCHAR ) {
13060             int i;
13061
13062             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13063                 cpThinkOutput[i] = '.';
13064             }
13065             cpThinkOutput[i] = NULLCHAR;
13066             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13067         }
13068     }
13069
13070     if (moveNumber == forwardMostMove - 1 &&
13071         gameInfo.resultDetails != NULL) {
13072         if (gameInfo.resultDetails[0] == NULLCHAR) {
13073             sprintf(res, " %s", PGNResult(gameInfo.result));
13074         } else {
13075             sprintf(res, " {%s} %s",
13076                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13077         }
13078     } else {
13079         res[0] = NULLCHAR;
13080     }
13081
13082     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13083         DisplayMessage(res, cpThinkOutput);
13084     } else {
13085         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13086                 WhiteOnMove(moveNumber) ? " " : ".. ",
13087                 parseList[moveNumber], res);
13088         DisplayMessage(message, cpThinkOutput);
13089     }
13090 }
13091
13092 void
13093 DisplayComment(moveNumber, text)
13094      int moveNumber;
13095      char *text;
13096 {
13097     char title[MSG_SIZ];
13098     char buf[8000]; // comment can be long!
13099     int score, depth;
13100     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13101       strcpy(title, "Comment");
13102     } else {
13103       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13104               WhiteOnMove(moveNumber) ? " " : ".. ",
13105               parseList[moveNumber]);
13106     }
13107     // [HGM] PV info: display PV info together with (or as) comment
13108     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13109       if(text == NULL) text = "";                                           
13110       score = pvInfoList[moveNumber].score;
13111       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13112               depth, (pvInfoList[moveNumber].time+50)/100, text);
13113       text = buf;
13114     }
13115     if (text != NULL && (appData.autoDisplayComment || commentUp))
13116       CommentPopUp(title, text);
13117 }
13118
13119 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13120  * might be busy thinking or pondering.  It can be omitted if your
13121  * gnuchess is configured to stop thinking immediately on any user
13122  * input.  However, that gnuchess feature depends on the FIONREAD
13123  * ioctl, which does not work properly on some flavors of Unix.
13124  */
13125 void
13126 Attention(cps)
13127      ChessProgramState *cps;
13128 {
13129 #if ATTENTION
13130     if (!cps->useSigint) return;
13131     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13132     switch (gameMode) {
13133       case MachinePlaysWhite:
13134       case MachinePlaysBlack:
13135       case TwoMachinesPlay:
13136       case IcsPlayingWhite:
13137       case IcsPlayingBlack:
13138       case AnalyzeMode:
13139       case AnalyzeFile:
13140         /* Skip if we know it isn't thinking */
13141         if (!cps->maybeThinking) return;
13142         if (appData.debugMode)
13143           fprintf(debugFP, "Interrupting %s\n", cps->which);
13144         InterruptChildProcess(cps->pr);
13145         cps->maybeThinking = FALSE;
13146         break;
13147       default:
13148         break;
13149     }
13150 #endif /*ATTENTION*/
13151 }
13152
13153 int
13154 CheckFlags()
13155 {
13156     if (whiteTimeRemaining <= 0) {
13157         if (!whiteFlag) {
13158             whiteFlag = TRUE;
13159             if (appData.icsActive) {
13160                 if (appData.autoCallFlag &&
13161                     gameMode == IcsPlayingBlack && !blackFlag) {
13162                   SendToICS(ics_prefix);
13163                   SendToICS("flag\n");
13164                 }
13165             } else {
13166                 if (blackFlag) {
13167                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13168                 } else {
13169                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13170                     if (appData.autoCallFlag) {
13171                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13172                         return TRUE;
13173                     }
13174                 }
13175             }
13176         }
13177     }
13178     if (blackTimeRemaining <= 0) {
13179         if (!blackFlag) {
13180             blackFlag = TRUE;
13181             if (appData.icsActive) {
13182                 if (appData.autoCallFlag &&
13183                     gameMode == IcsPlayingWhite && !whiteFlag) {
13184                   SendToICS(ics_prefix);
13185                   SendToICS("flag\n");
13186                 }
13187             } else {
13188                 if (whiteFlag) {
13189                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13190                 } else {
13191                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13192                     if (appData.autoCallFlag) {
13193                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13194                         return TRUE;
13195                     }
13196                 }
13197             }
13198         }
13199     }
13200     return FALSE;
13201 }
13202
13203 void
13204 CheckTimeControl()
13205 {
13206     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13207         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13208
13209     /*
13210      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13211      */
13212     if ( !WhiteOnMove(forwardMostMove) )
13213         /* White made time control */
13214         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13215         /* [HGM] time odds: correct new time quota for time odds! */
13216                                             / WhitePlayer()->timeOdds;
13217       else
13218         /* Black made time control */
13219         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13220                                             / WhitePlayer()->other->timeOdds;
13221 }
13222
13223 void
13224 DisplayBothClocks()
13225 {
13226     int wom = gameMode == EditPosition ?
13227       !blackPlaysFirst : WhiteOnMove(currentMove);
13228     DisplayWhiteClock(whiteTimeRemaining, wom);
13229     DisplayBlackClock(blackTimeRemaining, !wom);
13230 }
13231
13232
13233 /* Timekeeping seems to be a portability nightmare.  I think everyone
13234    has ftime(), but I'm really not sure, so I'm including some ifdefs
13235    to use other calls if you don't.  Clocks will be less accurate if
13236    you have neither ftime nor gettimeofday.
13237 */
13238
13239 /* VS 2008 requires the #include outside of the function */
13240 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13241 #include <sys/timeb.h>
13242 #endif
13243
13244 /* Get the current time as a TimeMark */
13245 void
13246 GetTimeMark(tm)
13247      TimeMark *tm;
13248 {
13249 #if HAVE_GETTIMEOFDAY
13250
13251     struct timeval timeVal;
13252     struct timezone timeZone;
13253
13254     gettimeofday(&timeVal, &timeZone);
13255     tm->sec = (long) timeVal.tv_sec;
13256     tm->ms = (int) (timeVal.tv_usec / 1000L);
13257
13258 #else /*!HAVE_GETTIMEOFDAY*/
13259 #if HAVE_FTIME
13260
13261 // include <sys/timeb.h> / moved to just above start of function
13262     struct timeb timeB;
13263
13264     ftime(&timeB);
13265     tm->sec = (long) timeB.time;
13266     tm->ms = (int) timeB.millitm;
13267
13268 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13269     tm->sec = (long) time(NULL);
13270     tm->ms = 0;
13271 #endif
13272 #endif
13273 }
13274
13275 /* Return the difference in milliseconds between two
13276    time marks.  We assume the difference will fit in a long!
13277 */
13278 long
13279 SubtractTimeMarks(tm2, tm1)
13280      TimeMark *tm2, *tm1;
13281 {
13282     return 1000L*(tm2->sec - tm1->sec) +
13283            (long) (tm2->ms - tm1->ms);
13284 }
13285
13286
13287 /*
13288  * Code to manage the game clocks.
13289  *
13290  * In tournament play, black starts the clock and then white makes a move.
13291  * We give the human user a slight advantage if he is playing white---the
13292  * clocks don't run until he makes his first move, so it takes zero time.
13293  * Also, we don't account for network lag, so we could get out of sync
13294  * with GNU Chess's clock -- but then, referees are always right.
13295  */
13296
13297 static TimeMark tickStartTM;
13298 static long intendedTickLength;
13299
13300 long
13301 NextTickLength(timeRemaining)
13302      long timeRemaining;
13303 {
13304     long nominalTickLength, nextTickLength;
13305
13306     if (timeRemaining > 0L && timeRemaining <= 10000L)
13307       nominalTickLength = 100L;
13308     else
13309       nominalTickLength = 1000L;
13310     nextTickLength = timeRemaining % nominalTickLength;
13311     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13312
13313     return nextTickLength;
13314 }
13315
13316 /* Adjust clock one minute up or down */
13317 void
13318 AdjustClock(Boolean which, int dir)
13319 {
13320     if(which) blackTimeRemaining += 60000*dir;
13321     else      whiteTimeRemaining += 60000*dir;
13322     DisplayBothClocks();
13323 }
13324
13325 /* Stop clocks and reset to a fresh time control */
13326 void
13327 ResetClocks()
13328 {
13329     (void) StopClockTimer();
13330     if (appData.icsActive) {
13331         whiteTimeRemaining = blackTimeRemaining = 0;
13332     } else if (searchTime) {
13333         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13334         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13335     } else { /* [HGM] correct new time quote for time odds */
13336         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13337         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13338     }
13339     if (whiteFlag || blackFlag) {
13340         DisplayTitle("");
13341         whiteFlag = blackFlag = FALSE;
13342     }
13343     DisplayBothClocks();
13344 }
13345
13346 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13347
13348 /* Decrement running clock by amount of time that has passed */
13349 void
13350 DecrementClocks()
13351 {
13352     long timeRemaining;
13353     long lastTickLength, fudge;
13354     TimeMark now;
13355
13356     if (!appData.clockMode) return;
13357     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13358
13359     GetTimeMark(&now);
13360
13361     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13362
13363     /* Fudge if we woke up a little too soon */
13364     fudge = intendedTickLength - lastTickLength;
13365     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13366
13367     if (WhiteOnMove(forwardMostMove)) {
13368         if(whiteNPS >= 0) lastTickLength = 0;
13369         timeRemaining = whiteTimeRemaining -= lastTickLength;
13370         DisplayWhiteClock(whiteTimeRemaining - fudge,
13371                           WhiteOnMove(currentMove));
13372     } else {
13373         if(blackNPS >= 0) lastTickLength = 0;
13374         timeRemaining = blackTimeRemaining -= lastTickLength;
13375         DisplayBlackClock(blackTimeRemaining - fudge,
13376                           !WhiteOnMove(currentMove));
13377     }
13378
13379     if (CheckFlags()) return;
13380
13381     tickStartTM = now;
13382     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13383     StartClockTimer(intendedTickLength);
13384
13385     /* if the time remaining has fallen below the alarm threshold, sound the
13386      * alarm. if the alarm has sounded and (due to a takeback or time control
13387      * with increment) the time remaining has increased to a level above the
13388      * threshold, reset the alarm so it can sound again.
13389      */
13390
13391     if (appData.icsActive && appData.icsAlarm) {
13392
13393         /* make sure we are dealing with the user's clock */
13394         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13395                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13396            )) return;
13397
13398         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13399             alarmSounded = FALSE;
13400         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13401             PlayAlarmSound();
13402             alarmSounded = TRUE;
13403         }
13404     }
13405 }
13406
13407
13408 /* A player has just moved, so stop the previously running
13409    clock and (if in clock mode) start the other one.
13410    We redisplay both clocks in case we're in ICS mode, because
13411    ICS gives us an update to both clocks after every move.
13412    Note that this routine is called *after* forwardMostMove
13413    is updated, so the last fractional tick must be subtracted
13414    from the color that is *not* on move now.
13415 */
13416 void
13417 SwitchClocks()
13418 {
13419     long lastTickLength;
13420     TimeMark now;
13421     int flagged = FALSE;
13422
13423     GetTimeMark(&now);
13424
13425     if (StopClockTimer() && appData.clockMode) {
13426         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13427         if (WhiteOnMove(forwardMostMove)) {
13428             if(blackNPS >= 0) lastTickLength = 0;
13429             blackTimeRemaining -= lastTickLength;
13430            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13431 //         if(pvInfoList[forwardMostMove-1].time == -1)
13432                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13433                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13434         } else {
13435            if(whiteNPS >= 0) lastTickLength = 0;
13436            whiteTimeRemaining -= lastTickLength;
13437            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13438 //         if(pvInfoList[forwardMostMove-1].time == -1)
13439                  pvInfoList[forwardMostMove-1].time =
13440                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13441         }
13442         flagged = CheckFlags();
13443     }
13444     CheckTimeControl();
13445
13446     if (flagged || !appData.clockMode) return;
13447
13448     switch (gameMode) {
13449       case MachinePlaysBlack:
13450       case MachinePlaysWhite:
13451       case BeginningOfGame:
13452         if (pausing) return;
13453         break;
13454
13455       case EditGame:
13456       case PlayFromGameFile:
13457       case IcsExamining:
13458         return;
13459
13460       default:
13461         break;
13462     }
13463
13464     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13465         if(WhiteOnMove(forwardMostMove))
13466              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13467         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13468     }
13469
13470     tickStartTM = now;
13471     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13472       whiteTimeRemaining : blackTimeRemaining);
13473     StartClockTimer(intendedTickLength);
13474 }
13475
13476
13477 /* Stop both clocks */
13478 void
13479 StopClocks()
13480 {
13481     long lastTickLength;
13482     TimeMark now;
13483
13484     if (!StopClockTimer()) return;
13485     if (!appData.clockMode) return;
13486
13487     GetTimeMark(&now);
13488
13489     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13490     if (WhiteOnMove(forwardMostMove)) {
13491         if(whiteNPS >= 0) lastTickLength = 0;
13492         whiteTimeRemaining -= lastTickLength;
13493         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13494     } else {
13495         if(blackNPS >= 0) lastTickLength = 0;
13496         blackTimeRemaining -= lastTickLength;
13497         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13498     }
13499     CheckFlags();
13500 }
13501
13502 /* Start clock of player on move.  Time may have been reset, so
13503    if clock is already running, stop and restart it. */
13504 void
13505 StartClocks()
13506 {
13507     (void) StopClockTimer(); /* in case it was running already */
13508     DisplayBothClocks();
13509     if (CheckFlags()) return;
13510
13511     if (!appData.clockMode) return;
13512     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13513
13514     GetTimeMark(&tickStartTM);
13515     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13516       whiteTimeRemaining : blackTimeRemaining);
13517
13518    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13519     whiteNPS = blackNPS = -1;
13520     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13521        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13522         whiteNPS = first.nps;
13523     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13524        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13525         blackNPS = first.nps;
13526     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13527         whiteNPS = second.nps;
13528     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13529         blackNPS = second.nps;
13530     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13531
13532     StartClockTimer(intendedTickLength);
13533 }
13534
13535 char *
13536 TimeString(ms)
13537      long ms;
13538 {
13539     long second, minute, hour, day;
13540     char *sign = "";
13541     static char buf[32];
13542
13543     if (ms > 0 && ms <= 9900) {
13544       /* convert milliseconds to tenths, rounding up */
13545       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13546
13547       sprintf(buf, " %03.1f ", tenths/10.0);
13548       return buf;
13549     }
13550
13551     /* convert milliseconds to seconds, rounding up */
13552     /* use floating point to avoid strangeness of integer division
13553        with negative dividends on many machines */
13554     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13555
13556     if (second < 0) {
13557         sign = "-";
13558         second = -second;
13559     }
13560
13561     day = second / (60 * 60 * 24);
13562     second = second % (60 * 60 * 24);
13563     hour = second / (60 * 60);
13564     second = second % (60 * 60);
13565     minute = second / 60;
13566     second = second % 60;
13567
13568     if (day > 0)
13569       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13570               sign, day, hour, minute, second);
13571     else if (hour > 0)
13572       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13573     else
13574       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13575
13576     return buf;
13577 }
13578
13579
13580 /*
13581  * This is necessary because some C libraries aren't ANSI C compliant yet.
13582  */
13583 char *
13584 StrStr(string, match)
13585      char *string, *match;
13586 {
13587     int i, length;
13588
13589     length = strlen(match);
13590
13591     for (i = strlen(string) - length; i >= 0; i--, string++)
13592       if (!strncmp(match, string, length))
13593         return string;
13594
13595     return NULL;
13596 }
13597
13598 char *
13599 StrCaseStr(string, match)
13600      char *string, *match;
13601 {
13602     int i, j, length;
13603
13604     length = strlen(match);
13605
13606     for (i = strlen(string) - length; i >= 0; i--, string++) {
13607         for (j = 0; j < length; j++) {
13608             if (ToLower(match[j]) != ToLower(string[j]))
13609               break;
13610         }
13611         if (j == length) return string;
13612     }
13613
13614     return NULL;
13615 }
13616
13617 #ifndef _amigados
13618 int
13619 StrCaseCmp(s1, s2)
13620      char *s1, *s2;
13621 {
13622     char c1, c2;
13623
13624     for (;;) {
13625         c1 = ToLower(*s1++);
13626         c2 = ToLower(*s2++);
13627         if (c1 > c2) return 1;
13628         if (c1 < c2) return -1;
13629         if (c1 == NULLCHAR) return 0;
13630     }
13631 }
13632
13633
13634 int
13635 ToLower(c)
13636      int c;
13637 {
13638     return isupper(c) ? tolower(c) : c;
13639 }
13640
13641
13642 int
13643 ToUpper(c)
13644      int c;
13645 {
13646     return islower(c) ? toupper(c) : c;
13647 }
13648 #endif /* !_amigados    */
13649
13650 char *
13651 StrSave(s)
13652      char *s;
13653 {
13654     char *ret;
13655
13656     if ((ret = (char *) malloc(strlen(s) + 1))) {
13657         strcpy(ret, s);
13658     }
13659     return ret;
13660 }
13661
13662 char *
13663 StrSavePtr(s, savePtr)
13664      char *s, **savePtr;
13665 {
13666     if (*savePtr) {
13667         free(*savePtr);
13668     }
13669     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13670         strcpy(*savePtr, s);
13671     }
13672     return(*savePtr);
13673 }
13674
13675 char *
13676 PGNDate()
13677 {
13678     time_t clock;
13679     struct tm *tm;
13680     char buf[MSG_SIZ];
13681
13682     clock = time((time_t *)NULL);
13683     tm = localtime(&clock);
13684     sprintf(buf, "%04d.%02d.%02d",
13685             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13686     return StrSave(buf);
13687 }
13688
13689
13690 char *
13691 PositionToFEN(move, overrideCastling)
13692      int move;
13693      char *overrideCastling;
13694 {
13695     int i, j, fromX, fromY, toX, toY;
13696     int whiteToPlay;
13697     char buf[128];
13698     char *p, *q;
13699     int emptycount;
13700     ChessSquare piece;
13701
13702     whiteToPlay = (gameMode == EditPosition) ?
13703       !blackPlaysFirst : (move % 2 == 0);
13704     p = buf;
13705
13706     /* Piece placement data */
13707     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13708         emptycount = 0;
13709         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13710             if (boards[move][i][j] == EmptySquare) {
13711                 emptycount++;
13712             } else { ChessSquare piece = boards[move][i][j];
13713                 if (emptycount > 0) {
13714                     if(emptycount<10) /* [HGM] can be >= 10 */
13715                         *p++ = '0' + emptycount;
13716                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13717                     emptycount = 0;
13718                 }
13719                 if(PieceToChar(piece) == '+') {
13720                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13721                     *p++ = '+';
13722                     piece = (ChessSquare)(DEMOTED piece);
13723                 }
13724                 *p++ = PieceToChar(piece);
13725                 if(p[-1] == '~') {
13726                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13727                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13728                     *p++ = '~';
13729                 }
13730             }
13731         }
13732         if (emptycount > 0) {
13733             if(emptycount<10) /* [HGM] can be >= 10 */
13734                 *p++ = '0' + emptycount;
13735             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13736             emptycount = 0;
13737         }
13738         *p++ = '/';
13739     }
13740     *(p - 1) = ' ';
13741
13742     /* [HGM] print Crazyhouse or Shogi holdings */
13743     if( gameInfo.holdingsWidth ) {
13744         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13745         q = p;
13746         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13747             piece = boards[move][i][BOARD_WIDTH-1];
13748             if( piece != EmptySquare )
13749               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13750                   *p++ = PieceToChar(piece);
13751         }
13752         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13753             piece = boards[move][BOARD_HEIGHT-i-1][0];
13754             if( piece != EmptySquare )
13755               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13756                   *p++ = PieceToChar(piece);
13757         }
13758
13759         if( q == p ) *p++ = '-';
13760         *p++ = ']';
13761         *p++ = ' ';
13762     }
13763
13764     /* Active color */
13765     *p++ = whiteToPlay ? 'w' : 'b';
13766     *p++ = ' ';
13767
13768   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13769     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13770   } else {
13771   if(nrCastlingRights) {
13772      q = p;
13773      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13774        /* [HGM] write directly from rights */
13775            if(boards[move][CASTLING][2] != NoRights &&
13776               boards[move][CASTLING][0] != NoRights   )
13777                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13778            if(boards[move][CASTLING][2] != NoRights &&
13779               boards[move][CASTLING][1] != NoRights   )
13780                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13781            if(boards[move][CASTLING][5] != NoRights &&
13782               boards[move][CASTLING][3] != NoRights   )
13783                 *p++ = boards[move][CASTLING][3] + AAA;
13784            if(boards[move][CASTLING][5] != NoRights &&
13785               boards[move][CASTLING][4] != NoRights   )
13786                 *p++ = boards[move][CASTLING][4] + AAA;
13787      } else {
13788
13789         /* [HGM] write true castling rights */
13790         if( nrCastlingRights == 6 ) {
13791             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13792                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13793             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13794                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13795             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13796                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13797             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13798                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13799         }
13800      }
13801      if (q == p) *p++ = '-'; /* No castling rights */
13802      *p++ = ' ';
13803   }
13804
13805   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13806      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13807     /* En passant target square */
13808     if (move > backwardMostMove) {
13809         fromX = moveList[move - 1][0] - AAA;
13810         fromY = moveList[move - 1][1] - ONE;
13811         toX = moveList[move - 1][2] - AAA;
13812         toY = moveList[move - 1][3] - ONE;
13813         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13814             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13815             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13816             fromX == toX) {
13817             /* 2-square pawn move just happened */
13818             *p++ = toX + AAA;
13819             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13820         } else {
13821             *p++ = '-';
13822         }
13823     } else if(move == backwardMostMove) {
13824         // [HGM] perhaps we should always do it like this, and forget the above?
13825         if((signed char)boards[move][EP_STATUS] >= 0) {
13826             *p++ = boards[move][EP_STATUS] + AAA;
13827             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13828         } else {
13829             *p++ = '-';
13830         }
13831     } else {
13832         *p++ = '-';
13833     }
13834     *p++ = ' ';
13835   }
13836   }
13837
13838     /* [HGM] find reversible plies */
13839     {   int i = 0, j=move;
13840
13841         if (appData.debugMode) { int k;
13842             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13843             for(k=backwardMostMove; k<=forwardMostMove; k++)
13844                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13845
13846         }
13847
13848         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13849         if( j == backwardMostMove ) i += initialRulePlies;
13850         sprintf(p, "%d ", i);
13851         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13852     }
13853     /* Fullmove number */
13854     sprintf(p, "%d", (move / 2) + 1);
13855
13856     return StrSave(buf);
13857 }
13858
13859 Boolean
13860 ParseFEN(board, blackPlaysFirst, fen)
13861     Board board;
13862      int *blackPlaysFirst;
13863      char *fen;
13864 {
13865     int i, j;
13866     char *p;
13867     int emptycount;
13868     ChessSquare piece;
13869
13870     p = fen;
13871
13872     /* [HGM] by default clear Crazyhouse holdings, if present */
13873     if(gameInfo.holdingsWidth) {
13874        for(i=0; i<BOARD_HEIGHT; i++) {
13875            board[i][0]             = EmptySquare; /* black holdings */
13876            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13877            board[i][1]             = (ChessSquare) 0; /* black counts */
13878            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13879        }
13880     }
13881
13882     /* Piece placement data */
13883     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13884         j = 0;
13885         for (;;) {
13886             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13887                 if (*p == '/') p++;
13888                 emptycount = gameInfo.boardWidth - j;
13889                 while (emptycount--)
13890                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13891                 break;
13892 #if(BOARD_FILES >= 10)
13893             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13894                 p++; emptycount=10;
13895                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13896                 while (emptycount--)
13897                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13898 #endif
13899             } else if (isdigit(*p)) {
13900                 emptycount = *p++ - '0';
13901                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13902                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13903                 while (emptycount--)
13904                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13905             } else if (*p == '+' || isalpha(*p)) {
13906                 if (j >= gameInfo.boardWidth) return FALSE;
13907                 if(*p=='+') {
13908                     piece = CharToPiece(*++p);
13909                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13910                     piece = (ChessSquare) (PROMOTED piece ); p++;
13911                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13912                 } else piece = CharToPiece(*p++);
13913
13914                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13915                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13916                     piece = (ChessSquare) (PROMOTED piece);
13917                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13918                     p++;
13919                 }
13920                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13921             } else {
13922                 return FALSE;
13923             }
13924         }
13925     }
13926     while (*p == '/' || *p == ' ') p++;
13927
13928     /* [HGM] look for Crazyhouse holdings here */
13929     while(*p==' ') p++;
13930     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13931         if(*p == '[') p++;
13932         if(*p == '-' ) *p++; /* empty holdings */ else {
13933             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13934             /* if we would allow FEN reading to set board size, we would   */
13935             /* have to add holdings and shift the board read so far here   */
13936             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13937                 *p++;
13938                 if((int) piece >= (int) BlackPawn ) {
13939                     i = (int)piece - (int)BlackPawn;
13940                     i = PieceToNumber((ChessSquare)i);
13941                     if( i >= gameInfo.holdingsSize ) return FALSE;
13942                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13943                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13944                 } else {
13945                     i = (int)piece - (int)WhitePawn;
13946                     i = PieceToNumber((ChessSquare)i);
13947                     if( i >= gameInfo.holdingsSize ) return FALSE;
13948                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13949                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13950                 }
13951             }
13952         }
13953         if(*p == ']') *p++;
13954     }
13955
13956     while(*p == ' ') p++;
13957
13958     /* Active color */
13959     switch (*p++) {
13960       case 'w':
13961         *blackPlaysFirst = FALSE;
13962         break;
13963       case 'b':
13964         *blackPlaysFirst = TRUE;
13965         break;
13966       default:
13967         return FALSE;
13968     }
13969
13970     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13971     /* return the extra info in global variiables             */
13972
13973     /* set defaults in case FEN is incomplete */
13974     board[EP_STATUS] = EP_UNKNOWN;
13975     for(i=0; i<nrCastlingRights; i++ ) {
13976         board[CASTLING][i] =
13977             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13978     }   /* assume possible unless obviously impossible */
13979     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13980     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13981     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13982     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13983     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13984     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13985     FENrulePlies = 0;
13986
13987     while(*p==' ') p++;
13988     if(nrCastlingRights) {
13989       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13990           /* castling indicator present, so default becomes no castlings */
13991           for(i=0; i<nrCastlingRights; i++ ) {
13992                  board[CASTLING][i] = NoRights;
13993           }
13994       }
13995       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13996              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13997              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13998              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13999         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14000
14001         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14002             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14003             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14004         }
14005         switch(c) {
14006           case'K':
14007               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14008               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14009               board[CASTLING][2] = whiteKingFile;
14010               break;
14011           case'Q':
14012               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14013               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14014               board[CASTLING][2] = whiteKingFile;
14015               break;
14016           case'k':
14017               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14018               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14019               board[CASTLING][5] = blackKingFile;
14020               break;
14021           case'q':
14022               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14023               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14024               board[CASTLING][5] = blackKingFile;
14025           case '-':
14026               break;
14027           default: /* FRC castlings */
14028               if(c >= 'a') { /* black rights */
14029                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14030                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14031                   if(i == BOARD_RGHT) break;
14032                   board[CASTLING][5] = i;
14033                   c -= AAA;
14034                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14035                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14036                   if(c > i)
14037                       board[CASTLING][3] = c;
14038                   else
14039                       board[CASTLING][4] = c;
14040               } else { /* white rights */
14041                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14042                     if(board[0][i] == WhiteKing) break;
14043                   if(i == BOARD_RGHT) break;
14044                   board[CASTLING][2] = i;
14045                   c -= AAA - 'a' + 'A';
14046                   if(board[0][c] >= WhiteKing) break;
14047                   if(c > i)
14048                       board[CASTLING][0] = c;
14049                   else
14050                       board[CASTLING][1] = c;
14051               }
14052         }
14053       }
14054     if (appData.debugMode) {
14055         fprintf(debugFP, "FEN castling rights:");
14056         for(i=0; i<nrCastlingRights; i++)
14057         fprintf(debugFP, " %d", board[CASTLING][i]);
14058         fprintf(debugFP, "\n");
14059     }
14060
14061       while(*p==' ') p++;
14062     }
14063
14064     /* read e.p. field in games that know e.p. capture */
14065     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14066        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14067       if(*p=='-') {
14068         p++; board[EP_STATUS] = EP_NONE;
14069       } else {
14070          char c = *p++ - AAA;
14071
14072          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14073          if(*p >= '0' && *p <='9') *p++;
14074          board[EP_STATUS] = c;
14075       }
14076     }
14077
14078
14079     if(sscanf(p, "%d", &i) == 1) {
14080         FENrulePlies = i; /* 50-move ply counter */
14081         /* (The move number is still ignored)    */
14082     }
14083
14084     return TRUE;
14085 }
14086
14087 void
14088 EditPositionPasteFEN(char *fen)
14089 {
14090   if (fen != NULL) {
14091     Board initial_position;
14092
14093     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14094       DisplayError(_("Bad FEN position in clipboard"), 0);
14095       return ;
14096     } else {
14097       int savedBlackPlaysFirst = blackPlaysFirst;
14098       EditPositionEvent();
14099       blackPlaysFirst = savedBlackPlaysFirst;
14100       CopyBoard(boards[0], initial_position);
14101       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14102       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14103       DisplayBothClocks();
14104       DrawPosition(FALSE, boards[currentMove]);
14105     }
14106   }
14107 }
14108
14109 static char cseq[12] = "\\   ";
14110
14111 Boolean set_cont_sequence(char *new_seq)
14112 {
14113     int len;
14114     Boolean ret;
14115
14116     // handle bad attempts to set the sequence
14117         if (!new_seq)
14118                 return 0; // acceptable error - no debug
14119
14120     len = strlen(new_seq);
14121     ret = (len > 0) && (len < sizeof(cseq));
14122     if (ret)
14123         strcpy(cseq, new_seq);
14124     else if (appData.debugMode)
14125         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14126     return ret;
14127 }
14128
14129 /*
14130     reformat a source message so words don't cross the width boundary.  internal
14131     newlines are not removed.  returns the wrapped size (no null character unless
14132     included in source message).  If dest is NULL, only calculate the size required
14133     for the dest buffer.  lp argument indicats line position upon entry, and it's
14134     passed back upon exit.
14135 */
14136 int wrap(char *dest, char *src, int count, int width, int *lp)
14137 {
14138     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14139
14140     cseq_len = strlen(cseq);
14141     old_line = line = *lp;
14142     ansi = len = clen = 0;
14143
14144     for (i=0; i < count; i++)
14145     {
14146         if (src[i] == '\033')
14147             ansi = 1;
14148
14149         // if we hit the width, back up
14150         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14151         {
14152             // store i & len in case the word is too long
14153             old_i = i, old_len = len;
14154
14155             // find the end of the last word
14156             while (i && src[i] != ' ' && src[i] != '\n')
14157             {
14158                 i--;
14159                 len--;
14160             }
14161
14162             // word too long?  restore i & len before splitting it
14163             if ((old_i-i+clen) >= width)
14164             {
14165                 i = old_i;
14166                 len = old_len;
14167             }
14168
14169             // extra space?
14170             if (i && src[i-1] == ' ')
14171                 len--;
14172
14173             if (src[i] != ' ' && src[i] != '\n')
14174             {
14175                 i--;
14176                 if (len)
14177                     len--;
14178             }
14179
14180             // now append the newline and continuation sequence
14181             if (dest)
14182                 dest[len] = '\n';
14183             len++;
14184             if (dest)
14185                 strncpy(dest+len, cseq, cseq_len);
14186             len += cseq_len;
14187             line = cseq_len;
14188             clen = cseq_len;
14189             continue;
14190         }
14191
14192         if (dest)
14193             dest[len] = src[i];
14194         len++;
14195         if (!ansi)
14196             line++;
14197         if (src[i] == '\n')
14198             line = 0;
14199         if (src[i] == 'm')
14200             ansi = 0;
14201     }
14202     if (dest && appData.debugMode)
14203     {
14204         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14205             count, width, line, len, *lp);
14206         show_bytes(debugFP, src, count);
14207         fprintf(debugFP, "\ndest: ");
14208         show_bytes(debugFP, dest, len);
14209         fprintf(debugFP, "\n");
14210     }
14211     *lp = dest ? line : old_line;
14212
14213     return len;
14214 }
14215
14216 // [HGM] vari: routines for shelving variations
14217
14218 void 
14219 PushTail(int firstMove, int lastMove)
14220 {
14221         int i, j, nrMoves = lastMove - firstMove;
14222
14223         if(appData.icsActive) { // only in local mode
14224                 forwardMostMove = currentMove; // mimic old ICS behavior
14225                 return;
14226         }
14227         if(storedGames >= MAX_VARIATIONS-1) return;
14228
14229         // push current tail of game on stack
14230         savedResult[storedGames] = gameInfo.result;
14231         savedDetails[storedGames] = gameInfo.resultDetails;
14232         gameInfo.resultDetails = NULL;
14233         savedFirst[storedGames] = firstMove;
14234         savedLast [storedGames] = lastMove;
14235         savedFramePtr[storedGames] = framePtr;
14236         framePtr -= nrMoves; // reserve space for the boards
14237         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14238             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14239             for(j=0; j<MOVE_LEN; j++)
14240                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14241             for(j=0; j<2*MOVE_LEN; j++)
14242                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14243             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14244             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14245             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14246             pvInfoList[firstMove+i-1].depth = 0;
14247             commentList[framePtr+i] = commentList[firstMove+i];
14248             commentList[firstMove+i] = NULL;
14249         }
14250
14251         storedGames++;
14252         forwardMostMove = currentMove; // truncte game so we can start variation
14253         if(storedGames == 1) GreyRevert(FALSE);
14254 }
14255
14256 Boolean
14257 PopTail(Boolean annotate)
14258 {
14259         int i, j, nrMoves;
14260         char buf[8000], moveBuf[20];
14261
14262         if(appData.icsActive) return FALSE; // only in local mode
14263         if(!storedGames) return FALSE; // sanity
14264
14265         storedGames--;
14266         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14267         nrMoves = savedLast[storedGames] - currentMove;
14268         if(annotate) {
14269                 int cnt = 10;
14270                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14271                 else strcpy(buf, "(");
14272                 for(i=currentMove; i<forwardMostMove; i++) {
14273                         if(WhiteOnMove(i))
14274                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14275                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14276                         strcat(buf, moveBuf);
14277                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14278                 }
14279                 strcat(buf, ")");
14280         }
14281         for(i=1; i<nrMoves; i++) { // copy last variation back
14282             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14283             for(j=0; j<MOVE_LEN; j++)
14284                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14285             for(j=0; j<2*MOVE_LEN; j++)
14286                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14287             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14288             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14289             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14290             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14291             commentList[currentMove+i] = commentList[framePtr+i];
14292             commentList[framePtr+i] = NULL;
14293         }
14294         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14295         framePtr = savedFramePtr[storedGames];
14296         gameInfo.result = savedResult[storedGames];
14297         if(gameInfo.resultDetails != NULL) {
14298             free(gameInfo.resultDetails);
14299       }
14300         gameInfo.resultDetails = savedDetails[storedGames];
14301         forwardMostMove = currentMove + nrMoves;
14302         if(storedGames == 0) GreyRevert(TRUE);
14303         return TRUE;
14304 }
14305
14306 void 
14307 CleanupTail()
14308 {       // remove all shelved variations
14309         int i;
14310         for(i=0; i<storedGames; i++) {
14311             if(savedDetails[i])
14312                 free(savedDetails[i]);
14313             savedDetails[i] = NULL;
14314         }
14315         for(i=framePtr; i<MAX_MOVES; i++) {
14316                 if(commentList[i]) free(commentList[i]);
14317                 commentList[i] = NULL;
14318         }
14319         framePtr = MAX_MOVES-1;
14320         storedGames = 0;
14321 }