2 * backend.c -- Common back end for X and Windows NT versions of
\r
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
\r
5 * Copyright 1991 by Digital Equipment Corporation, Maynard,
\r
6 * Massachusetts. Enhancements Copyright
\r
7 * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
\r
10 * The following terms apply to Digital Equipment Corporation's copyright
\r
11 * interest in XBoard:
\r
12 * ------------------------------------------------------------------------
\r
13 * All Rights Reserved
\r
15 * Permission to use, copy, modify, and distribute this software and its
\r
16 * documentation for any purpose and without fee is hereby granted,
\r
17 * provided that the above copyright notice appear in all copies and that
\r
18 * both that copyright notice and this permission notice appear in
\r
19 * supporting documentation, and that the name of Digital not be
\r
20 * used in advertising or publicity pertaining to distribution of the
\r
21 * software without specific, written prior permission.
\r
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
30 * ------------------------------------------------------------------------
\r
32 * The following terms apply to the enhanced version of XBoard
\r
33 * distributed by the Free Software Foundation:
\r
34 * ------------------------------------------------------------------------
\r
36 * GNU XBoard is free software: you can redistribute it and/or modify
\r
37 * it under the terms of the GNU General Public License as published by
\r
38 * the Free Software Foundation, either version 3 of the License, or (at
\r
39 * your option) any later version.
\r
41 * GNU XBoard is distributed in the hope that it will be useful, but
\r
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
\r
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
44 * General Public License for more details.
\r
46 * You should have received a copy of the GNU General Public License
\r
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
\r
49 *------------------------------------------------------------------------
\r
50 ** See the file ChangeLog for a revision history. */
\r
52 /* [AS] Also useful here for debugging */
\r
54 #include <windows.h>
\r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)
\r
70 #include <sys/types.h>
\r
71 #include <sys/stat.h>
\r
76 # include <stdlib.h>
\r
77 # include <string.h>
\r
78 #else /* not STDC_HEADERS */
\r
80 # include <string.h>
\r
81 # else /* not HAVE_STRING_H */
\r
82 # include <strings.h>
\r
83 # endif /* not HAVE_STRING_H */
\r
84 #endif /* not STDC_HEADERS */
\r
86 #if HAVE_SYS_FCNTL_H
\r
87 # include <sys/fcntl.h>
\r
88 #else /* not HAVE_SYS_FCNTL_H */
\r
91 # endif /* HAVE_FCNTL_H */
\r
92 #endif /* not HAVE_SYS_FCNTL_H */
\r
94 #if TIME_WITH_SYS_TIME
\r
95 # include <sys/time.h>
\r
98 # if HAVE_SYS_TIME_H
\r
99 # include <sys/time.h>
\r
105 #if defined(_amigados) && !defined(__GNUC__)
\r
107 int tz_minuteswest;
\r
110 extern int gettimeofday(struct timeval *, struct timezone *);
\r
114 # include <unistd.h>
\r
117 #include "common.h"
\r
118 #include "frontend.h"
\r
119 #include "backend.h"
\r
120 #include "parser.h"
\r
123 # include "zippy.h"
\r
125 #include "backendz.h"
\r
126 #include "gettext.h"
\r
129 # define _(s) gettext (s)
\r
130 # define N_(s) gettext_noop (s)
\r
137 /* A point in time */
\r
139 long sec; /* Assuming this is >= 32 bits */
\r
140 int ms; /* Assuming this is >= 16 bits */
\r
143 int establish P((void));
\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
145 char *buf, int count, int error));
\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
147 char *buf, int count, int error));
\r
148 void SendToICS P((char *s));
\r
149 void SendToICSDelayed P((char *s, long msdelay));
\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
151 int toX, int toY));
\r
152 void InitPosition P((int redraw));
\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
154 int AutoPlayOneMove P((void));
\r
155 int LoadGameOneMove P((ChessMove readAhead));
\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
158 int SavePositionToFile P((char *filename));
\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
164 /*char*/int promoChar));
\r
165 void BackwardInner P((int target));
\r
166 void ForwardInner P((int target));
\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
168 void EditPositionDone P((void));
\r
169 void PrintOpponents P((FILE *fp));
\r
170 void PrintPosition P((FILE *fp, int move));
\r
171 void StartChessProgram P((ChessProgramState *cps));
\r
172 void SendToProgram P((char *message, ChessProgramState *cps));
\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
175 char *buf, int count, int error));
\r
176 void SendTimeControl P((ChessProgramState *cps,
\r
177 int mps, long tc, int inc, int sd, int st));
\r
178 char *TimeControlTagValue P((void));
\r
179 void Attention P((ChessProgramState *cps));
\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
181 void ResurrectChessProgram P((void));
\r
182 void DisplayComment P((int moveNumber, char *text));
\r
183 void DisplayMove P((int moveNumber));
\r
184 void DisplayAnalysis P((void));
\r
186 void ParseGameHistory P((char *game));
\r
187 void ParseBoard12 P((char *string));
\r
188 void StartClocks P((void));
\r
189 void SwitchClocks P((void));
\r
190 void StopClocks P((void));
\r
191 void ResetClocks P((void));
\r
192 char *PGNDate P((void));
\r
193 void SetGameInfo P((void));
\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
195 int RegisterMove P((void));
\r
196 void MakeRegisteredMove P((void));
\r
197 void TruncateGame P((void));
\r
198 int looking_at P((char *, int *, char *));
\r
199 void CopyPlayerNameIntoFileName P((char **, char *));
\r
200 char *SavePart P((char *));
\r
201 int SaveGameOldStyle P((FILE *));
\r
202 int SaveGamePGN P((FILE *));
\r
203 void GetTimeMark P((TimeMark *));
\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
205 int CheckFlags P((void));
\r
206 long NextTickLength P((long));
\r
207 void CheckTimeControl P((void));
\r
208 void show_bytes P((FILE *, char *, int));
\r
209 int string_to_rating P((char *str));
\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
211 void InitBackEnd3 P((void));
\r
212 void FeatureDone P((ChessProgramState* cps, int val));
\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
214 void OutputKibitz(int window, char *text);
\r
215 int PerpetualChase(int first, int last);
\r
216 int EngineOutputIsUp();
\r
217 void InitDrawingSizes(int x, int y);
\r
220 extern void ConsoleCreate();
\r
223 ChessProgramState *WhitePlayer();
\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
225 int VerifyDisplayMode P(());
\r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
231 extern char installDir[MSG_SIZ];
\r
233 extern int tinyLayout, smallLayout;
\r
234 ChessProgramStats programStats;
\r
235 static int exiting = 0; /* [HGM] moved to top */
\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
\r
237 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
238 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
241 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
242 int opponentKibitzes;
\r
244 /* States for ics_getting_history */
\r
246 #define H_REQUESTED 1
\r
247 #define H_GOT_REQ_HEADER 2
\r
248 #define H_GOT_UNREQ_HEADER 3
\r
249 #define H_GETTING_MOVES 4
\r
250 #define H_GOT_UNWANTED_HEADER 5
\r
252 /* whosays values for GameEnds */
\r
254 #define GE_ENGINE 1
\r
255 #define GE_PLAYER 2
\r
257 #define GE_XBOARD 4
\r
258 #define GE_ENGINE1 5
\r
259 #define GE_ENGINE2 6
\r
261 /* Maximum number of games in a cmail message */
\r
262 #define CMAIL_MAX_GAMES 20
\r
264 /* Different types of move when calling RegisterMove */
\r
265 #define CMAIL_MOVE 0
\r
266 #define CMAIL_RESIGN 1
\r
267 #define CMAIL_DRAW 2
\r
268 #define CMAIL_ACCEPT 3
\r
270 /* Different types of result to remember for each game */
\r
271 #define CMAIL_NOT_RESULT 0
\r
272 #define CMAIL_OLD_RESULT 1
\r
273 #define CMAIL_NEW_RESULT 2
\r
275 /* Telnet protocol constants */
\r
276 #define TN_WILL 0373
\r
277 #define TN_WONT 0374
\r
279 #define TN_DONT 0376
\r
280 #define TN_IAC 0377
\r
281 #define TN_ECHO 0001
\r
282 #define TN_SGA 0003
\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
288 assert( dst != NULL );
\r
289 assert( src != NULL );
\r
290 assert( count > 0 );
\r
292 strncpy( dst, src, count );
\r
293 dst[ count-1 ] = '\0';
\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.
\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
303 assert( dst != NULL );
\r
304 assert( src != NULL );
\r
305 assert( count > 0 );
\r
307 dst_len = strlen(dst);
\r
309 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
311 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
317 /* Some compiler can't cast u64 to double
\r
318 * This function do the job for us:
\r
320 * We use the highest bit for cast, this only
\r
321 * works if the highest bit is not
\r
322 * in use (This should not happen)
\r
324 * We used this for all compiler
\r
327 u64ToDouble(u64 value)
\r
330 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
331 r = (double)(s64)tmp;
\r
332 if (value & u64Const(0x8000000000000000))
\r
333 r += 9.2233720368547758080e18; /* 2^63 */
\r
337 /* Fake up flags for now, as we aren't keeping track of castling
\r
338 availability yet. [HGM] Change of logic: the flag now only
\r
339 indicates the type of castlings allowed by the rule of the game.
\r
340 The actual rights themselves are maintained in the array
\r
341 castlingRights, as part of the game history, and are not probed
\r
347 int flags = F_ALL_CASTLE_OK;
\r
348 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
349 switch (gameInfo.variant) {
\r
350 case VariantSuicide:
\r
351 flags &= ~F_ALL_CASTLE_OK;
\r
352 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
353 flags |= F_IGNORE_CHECK;
\r
354 case VariantLosers:
\r
355 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
\r
357 case VariantAtomic:
\r
358 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
360 case VariantKriegspiel:
\r
361 flags |= F_KRIEGSPIEL_CAPTURE;
\r
363 case VariantCapaRandom:
\r
364 case VariantFischeRandom:
\r
365 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
366 case VariantNoCastle:
\r
367 case VariantShatranj:
\r
368 case VariantCourier:
\r
369 flags &= ~F_ALL_CASTLE_OK;
\r
377 FILE *gameFileFP, *debugFP;
\r
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
381 into a fixed-size buffer. Because of this, we must be prepared to
\r
382 receive strings as long as the size of the input buffer, which is currently
\r
383 set to 4K for Windows and 8K for the rest.
\r
384 So, we must either allocate sufficiently large buffers here, or
\r
385 reduce the size of the input buffer in the input reading part.
\r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
390 char thinkOutput1[MSG_SIZ*10];
\r
392 ChessProgramState first, second;
\r
394 /* premove variables */
\r
395 int premoveToX = 0;
\r
396 int premoveToY = 0;
\r
397 int premoveFromX = 0;
\r
398 int premoveFromY = 0;
\r
399 int premovePromoChar = 0;
\r
400 int gotPremove = 0;
\r
401 Boolean alarmSounded;
\r
402 /* end premove variables */
\r
404 char *ics_prefix = "$";
\r
405 int ics_type = ICS_GENERIC;
\r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
408 int pauseExamForwardMostMove = 0;
\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
415 int whiteFlag = FALSE, blackFlag = FALSE;
\r
416 int userOfferedDraw = FALSE;
\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
419 int cmailMoveType[CMAIL_MAX_GAMES];
\r
420 long ics_clock_paused = 0;
\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
423 GameMode gameMode = BeginningOfGame;
\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
427 int hiddenThinkOutputState = 0; /* [AS] */
\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
429 int adjudicateLossPlies = 6;
\r
430 char white_holding[64], black_holding[64];
\r
431 TimeMark lastNodeCountTime;
\r
432 long lastNodeCount=0;
\r
433 int have_sent_ICS_logon = 0;
\r
434 int movesPerSession;
\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
436 long timeControl_2; /* [AS] Allow separate time controls */
\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
438 long timeRemaining[2][MAX_MOVES];
\r
440 TimeMark programStartTime;
\r
441 char ics_handle[MSG_SIZ];
\r
442 int have_set_title = 0;
\r
444 /* animateTraining preserves the state of appData.animate
\r
445 * when Training mode is activated. This allows the
\r
446 * response to be animated when appData.animate == TRUE and
\r
447 * appData.animateDragging == TRUE.
\r
449 Boolean animateTraining;
\r
455 Board boards[MAX_MOVES];
\r
456 /* [HGM] Following 7 needed for accurate legality tests: */
\r
457 char epStatus[MAX_MOVES];
\r
458 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
459 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
460 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
461 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
462 int initialRulePlies, FENrulePlies;
\r
464 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
466 int shuffleOpenings;
\r
468 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
469 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
470 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
471 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
472 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
476 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
477 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
478 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
479 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
482 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
483 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
484 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
485 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
486 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
490 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
491 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
492 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
493 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
497 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
498 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
499 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
500 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
504 #if (BOARD_SIZE>=10)
\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
506 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
507 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
508 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
509 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
513 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
514 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
515 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
516 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
520 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
521 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
522 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
523 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
527 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
528 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
529 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
530 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
534 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
535 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
536 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
537 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
543 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
545 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
548 #define GothicArray CapablancaArray
\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
553 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
554 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
555 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
556 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
559 #define FalconArray CapablancaArray
\r
562 #else // !(BOARD_SIZE>=10)
\r
563 #define XiangqiPosition FIDEArray
\r
564 #define CapablancaArray FIDEArray
\r
565 #define GothicArray FIDEArray
\r
566 #define GreatArray FIDEArray
\r
567 #endif // !(BOARD_SIZE>=10)
\r
569 #if (BOARD_SIZE>=12)
\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
571 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
572 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
573 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
574 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
576 #else // !(BOARD_SIZE>=12)
\r
577 #define CourierArray CapablancaArray
\r
578 #endif // !(BOARD_SIZE>=12)
\r
581 Board initialPosition;
\r
584 /* Convert str to a rating. Checks for special cases of "----",
\r
586 "++++", etc. Also strips ()'s */
\r
588 string_to_rating(str)
\r
591 while(*str && !isdigit(*str)) ++str;
\r
593 return 0; /* One of the special "no rating" cases */
\r
599 ClearProgramStats()
\r
601 /* Init programStats */
\r
602 programStats.movelist[0] = 0;
\r
603 programStats.depth = 0;
\r
604 programStats.nr_moves = 0;
\r
605 programStats.moves_left = 0;
\r
606 programStats.nodes = 0;
\r
607 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
608 programStats.score = 0;
\r
609 programStats.got_only_move = 0;
\r
610 programStats.got_fail = 0;
\r
611 programStats.line_is_book = 0;
\r
617 int matched, min, sec;
\r
619 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
621 GetTimeMark(&programStartTime);
\r
622 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
\r
624 ClearProgramStats();
\r
625 programStats.ok_to_send = 1;
\r
626 programStats.seen_stat = 0;
\r
629 * Initialize game list
\r
631 ListNew(&gameList);
\r
635 * Internet chess server status
\r
637 if (appData.icsActive) {
\r
638 appData.matchMode = FALSE;
\r
639 appData.matchGames = 0;
\r
641 appData.noChessProgram = !appData.zippyPlay;
\r
643 appData.zippyPlay = FALSE;
\r
644 appData.zippyTalk = FALSE;
\r
645 appData.noChessProgram = TRUE;
\r
647 if (*appData.icsHelper != NULLCHAR) {
\r
648 appData.useTelnet = TRUE;
\r
649 appData.telnetProgram = appData.icsHelper;
\r
652 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
655 /* [AS] Initialize pv info list [HGM] and game state */
\r
659 for( i=0; i<MAX_MOVES; i++ ) {
\r
660 pvInfoList[i].depth = -1;
\r
661 epStatus[i]=EP_NONE;
\r
662 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
667 * Parse timeControl resource
\r
669 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
670 appData.movesPerSession)) {
\r
672 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
673 DisplayFatalError(buf, 0, 2);
\r
677 * Parse searchTime resource
\r
679 if (*appData.searchTime != NULLCHAR) {
\r
680 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
681 if (matched == 1) {
\r
682 searchTime = min * 60;
\r
683 } else if (matched == 2) {
\r
684 searchTime = min * 60 + sec;
\r
687 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
688 DisplayFatalError(buf, 0, 2);
\r
692 /* [AS] Adjudication threshold */
\r
693 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
695 first.which = "first";
\r
696 second.which = "second";
\r
697 first.maybeThinking = second.maybeThinking = FALSE;
\r
698 first.pr = second.pr = NoProc;
\r
699 first.isr = second.isr = NULL;
\r
700 first.sendTime = second.sendTime = 2;
\r
701 first.sendDrawOffers = 1;
\r
702 if (appData.firstPlaysBlack) {
\r
703 first.twoMachinesColor = "black\n";
\r
704 second.twoMachinesColor = "white\n";
\r
706 first.twoMachinesColor = "white\n";
\r
707 second.twoMachinesColor = "black\n";
\r
709 first.program = appData.firstChessProgram;
\r
710 second.program = appData.secondChessProgram;
\r
711 first.host = appData.firstHost;
\r
712 second.host = appData.secondHost;
\r
713 first.dir = appData.firstDirectory;
\r
714 second.dir = appData.secondDirectory;
\r
715 first.other = &second;
\r
716 second.other = &first;
\r
717 first.initString = appData.initString;
\r
718 second.initString = appData.secondInitString;
\r
719 first.computerString = appData.firstComputerString;
\r
720 second.computerString = appData.secondComputerString;
\r
721 first.useSigint = second.useSigint = TRUE;
\r
722 first.useSigterm = second.useSigterm = TRUE;
\r
723 first.reuse = appData.reuseFirst;
\r
724 second.reuse = appData.reuseSecond;
\r
725 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
726 second.nps = appData.secondNPS;
\r
727 first.useSetboard = second.useSetboard = FALSE;
\r
728 first.useSAN = second.useSAN = FALSE;
\r
729 first.usePing = second.usePing = FALSE;
\r
730 first.lastPing = second.lastPing = 0;
\r
731 first.lastPong = second.lastPong = 0;
\r
732 first.usePlayother = second.usePlayother = FALSE;
\r
733 first.useColors = second.useColors = TRUE;
\r
734 first.useUsermove = second.useUsermove = FALSE;
\r
735 first.sendICS = second.sendICS = FALSE;
\r
736 first.sendName = second.sendName = appData.icsActive;
\r
737 first.sdKludge = second.sdKludge = FALSE;
\r
738 first.stKludge = second.stKludge = FALSE;
\r
739 TidyProgramName(first.program, first.host, first.tidy);
\r
740 TidyProgramName(second.program, second.host, second.tidy);
\r
741 first.matchWins = second.matchWins = 0;
\r
742 strcpy(first.variants, appData.variant);
\r
743 strcpy(second.variants, appData.variant);
\r
744 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
745 first.analyzing = second.analyzing = FALSE;
\r
746 first.initDone = second.initDone = FALSE;
\r
748 /* New features added by Tord: */
\r
749 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
750 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
751 /* End of new features added by Tord. */
\r
752 first.fenOverride = appData.fenOverride1;
\r
753 second.fenOverride = appData.fenOverride2;
\r
755 /* [HGM] time odds: set factor for each machine */
\r
756 first.timeOdds = appData.firstTimeOdds;
\r
757 second.timeOdds = appData.secondTimeOdds;
\r
759 if(appData.timeOddsMode) {
\r
760 norm = first.timeOdds;
\r
761 if(norm > second.timeOdds) norm = second.timeOdds;
\r
763 first.timeOdds /= norm;
\r
764 second.timeOdds /= norm;
\r
767 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
768 first.accumulateTC = appData.firstAccumulateTC;
\r
769 second.accumulateTC = appData.secondAccumulateTC;
\r
770 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
773 first.debug = second.debug = FALSE;
\r
774 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
776 /* [HGM] options */
\r
777 first.optionSettings = appData.firstOptions;
\r
778 second.optionSettings = appData.secondOptions;
\r
780 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
781 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
782 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
783 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
784 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
785 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
787 if (appData.firstProtocolVersion > PROTOVER ||
\r
788 appData.firstProtocolVersion < 1) {
\r
790 sprintf(buf, _("protocol version %d not supported"),
\r
791 appData.firstProtocolVersion);
\r
792 DisplayFatalError(buf, 0, 2);
\r
794 first.protocolVersion = appData.firstProtocolVersion;
\r
797 if (appData.secondProtocolVersion > PROTOVER ||
\r
798 appData.secondProtocolVersion < 1) {
\r
800 sprintf(buf, _("protocol version %d not supported"),
\r
801 appData.secondProtocolVersion);
\r
802 DisplayFatalError(buf, 0, 2);
\r
804 second.protocolVersion = appData.secondProtocolVersion;
\r
807 if (appData.icsActive) {
\r
808 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
809 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
810 appData.clockMode = FALSE;
\r
811 first.sendTime = second.sendTime = 0;
\r
815 /* Override some settings from environment variables, for backward
\r
816 compatibility. Unfortunately it's not feasible to have the env
\r
817 vars just set defaults, at least in xboard. Ugh.
\r
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
824 if (appData.noChessProgram) {
\r
825 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
826 + strlen(PATCHLEVEL));
\r
827 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
832 while (*q != ' ' && *q != NULLCHAR) q++;
\r
834 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
835 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
836 + strlen(PATCHLEVEL) + (q - p));
\r
837 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
838 strncat(programVersion, p, q - p);
\r
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
841 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
842 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
843 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
847 if (!appData.icsActive) {
\r
849 /* Check for variants that are supported only in ICS mode,
\r
850 or not at all. Some that are accepted here nevertheless
\r
851 have bugs; see comments below.
\r
853 VariantClass variant = StringToVariant(appData.variant);
\r
855 case VariantBughouse: /* need four players and two boards */
\r
856 case VariantKriegspiel: /* need to hide pieces and move details */
\r
857 /* case VariantFischeRandom: (Fabien: moved below) */
\r
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
859 DisplayFatalError(buf, 0, 2);
\r
862 case VariantUnknown:
\r
863 case VariantLoadable:
\r
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
874 DisplayFatalError(buf, 0, 2);
\r
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
879 case VariantGothic: /* [HGM] should work */
\r
880 case VariantCapablanca: /* [HGM] should work */
\r
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
882 case VariantShogi: /* [HGM] drops not tested for legality */
\r
883 case VariantKnightmate: /* [HGM] should work */
\r
884 case VariantCylinder: /* [HGM] untested */
\r
885 case VariantFalcon: /* [HGM] untested */
\r
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
887 offboard interposition not understood */
\r
888 case VariantNormal: /* definitely works! */
\r
889 case VariantWildCastle: /* pieces not automatically shuffled */
\r
890 case VariantNoCastle: /* pieces not automatically shuffled */
\r
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
892 case VariantLosers: /* should work except for win condition,
\r
893 and doesn't know captures are mandatory */
\r
894 case VariantSuicide: /* should work except for win condition,
\r
895 and doesn't know captures are mandatory */
\r
896 case VariantGiveaway: /* should work except for win condition,
\r
897 and doesn't know captures are mandatory */
\r
898 case VariantTwoKings: /* should work */
\r
899 case VariantAtomic: /* should work except for win condition */
\r
900 case Variant3Check: /* should work except for win condition */
\r
901 case VariantShatranj: /* should work except for all win conditions */
\r
902 case VariantBerolina: /* might work if TestLegality is off */
\r
903 case VariantCapaRandom: /* should work */
\r
904 case VariantJanus: /* should work */
\r
905 case VariantSuper: /* experimental */
\r
906 case VariantGreat: /* experimental, requires legality testing to be off */
\r
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
912 InitEngineUCI( installDir, &second );
\r
915 int NextIntegerFromString( char ** str, long * value )
\r
920 while( *s == ' ' || *s == '\t' ) {
\r
926 if( *s >= '0' && *s <= '9' ) {
\r
927 while( *s >= '0' && *s <= '9' ) {
\r
928 *value = *value * 10 + (*s - '0');
\r
940 int NextTimeControlFromString( char ** str, long * value )
\r
943 int result = NextIntegerFromString( str, &temp );
\r
945 if( result == 0 ) {
\r
946 *value = temp * 60; /* Minutes */
\r
947 if( **str == ':' ) {
\r
949 result = NextIntegerFromString( str, &temp );
\r
950 *value += temp; /* Seconds */
\r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
959 int result = -1; long temp, temp2;
\r
961 if(**str != '+') return -1; // old params remain in force!
\r
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
966 /* time only: incremental or sudden-death time control */
\r
967 if(**str == '+') { /* increment follows; read it */
\r
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
970 *inc = temp2 * 1000;
\r
972 *moves = 0; *tc = temp * 1000;
\r
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
976 (*str)++; /* classical time control */
\r
977 result = NextTimeControlFromString( str, &temp2);
\r
980 *tc = temp2 * 1000;
\r
986 int GetTimeQuota(int movenr)
\r
987 { /* [HGM] get time to add from the multi-session time-control string */
\r
988 int moves=1; /* kludge to force reading of first session */
\r
989 long time, increment;
\r
990 char *s = fullTimeControlString;
\r
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
996 if(movenr == -1) return time; /* last move before new session */
\r
997 if(!moves) return increment; /* current session is incremental */
\r
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
999 } while(movenr >= -1); /* try again for next session */
\r
1001 return 0; // no new time quota on this move
\r
1005 ParseTimeControl(tc, ti, mps)
\r
1011 int matched, min, sec;
\r
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1014 if (matched == 1) {
\r
1015 timeControl = min * 60 * 1000;
\r
1016 } else if (matched == 2) {
\r
1017 timeControl = (min * 60 + sec) * 1000;
\r
1024 char buf[MSG_SIZ];
\r
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1030 else sprintf(buf, "+%s+%d", tc, ti);
\r
1033 sprintf(buf, "+%d/%s", mps, tc);
\r
1034 else sprintf(buf, "+%s", tc);
\r
1036 fullTimeControlString = StrSave(buf);
\r
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1042 if( *tc == '/' ) {
\r
1043 /* Parse second time control */
\r
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1054 timeControl_2 = tc2 * 1000;
\r
1057 timeControl_2 = 0;
\r
1064 timeControl = tc1 * 1000;
\r
1068 timeIncrement = ti * 1000; /* convert to ms */
\r
1069 movesPerSession = 0;
\r
1071 timeIncrement = 0;
\r
1072 movesPerSession = mps;
\r
1080 if (appData.debugMode) {
\r
1081 fprintf(debugFP, "%s\n", programVersion);
\r
1084 if (appData.matchGames > 0) {
\r
1085 appData.matchMode = TRUE;
\r
1086 } else if (appData.matchMode) {
\r
1087 appData.matchGames = 1;
\r
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1090 appData.matchGames = appData.sameColorGames;
\r
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1095 Reset(TRUE, FALSE);
\r
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1099 /* kludge: allow timeout for initial "feature" commands */
\r
1101 DisplayMessage("", _("Starting chess program"));
\r
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1107 InitBackEnd3 P((void))
\r
1109 GameMode initialMode;
\r
1110 char buf[MSG_SIZ];
\r
1113 InitChessProgram(&first, startedFromSetupPosition);
\r
1116 if (appData.icsActive) {
\r
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1121 err = establish();
\r
1123 if (*appData.icsCommPort != NULLCHAR) {
\r
1124 sprintf(buf, _("Could not open comm port %s"),
\r
1125 appData.icsCommPort);
\r
1127 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1128 appData.icsHost, appData.icsPort);
\r
1130 DisplayFatalError(buf, err, 1);
\r
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1138 } else if (appData.noChessProgram) {
\r
1144 if (*appData.cmailGameName != NULLCHAR) {
\r
1146 OpenLoopback(&cmailPR);
\r
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1152 DisplayMessage("", "");
\r
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1154 initialMode = BeginningOfGame;
\r
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1156 initialMode = TwoMachinesPlay;
\r
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1158 initialMode = AnalyzeFile;
\r
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1160 initialMode = AnalyzeMode;
\r
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1162 initialMode = MachinePlaysWhite;
\r
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1164 initialMode = MachinePlaysBlack;
\r
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1166 initialMode = EditGame;
\r
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1168 initialMode = EditPosition;
\r
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1170 initialMode = Training;
\r
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1173 DisplayFatalError(buf, 0, 2);
\r
1177 if (appData.matchMode) {
\r
1178 /* Set up machine vs. machine match */
\r
1179 if (appData.noChessProgram) {
\r
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1186 if (*appData.loadGameFile != NULLCHAR) {
\r
1187 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1188 if(index<0) lastIndex = index = 1;
\r
1189 if (!LoadGameFromFile(appData.loadGameFile,
\r
1191 appData.loadGameFile, FALSE)) {
\r
1192 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1197 if(index<0) lastIndex = index = 1;
\r
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1200 appData.loadPositionFile)) {
\r
1201 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1205 TwoMachinesEvent();
\r
1206 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1207 /* Set up cmail mode */
\r
1208 ReloadCmailMsgEvent(TRUE);
\r
1210 /* Set up other modes */
\r
1211 if (initialMode == AnalyzeFile) {
\r
1212 if (*appData.loadGameFile == NULLCHAR) {
\r
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1217 if (*appData.loadGameFile != NULLCHAR) {
\r
1218 (void) LoadGameFromFile(appData.loadGameFile,
\r
1219 appData.loadGameIndex,
\r
1220 appData.loadGameFile, TRUE);
\r
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1223 appData.loadPositionIndex,
\r
1224 appData.loadPositionFile);
\r
1225 /* [HGM] try to make self-starting even after FEN load */
\r
1226 /* to allow automatic setup of fairy variants with wtm */
\r
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1228 gameMode = BeginningOfGame;
\r
1229 setboardSpoiledMachineBlack = 1;
\r
1231 /* [HGM] loadPos: make that every new game uses the setup */
\r
1232 /* from file as long as we do not switch variant */
\r
1233 if(!blackPlaysFirst) { int i;
\r
1234 startedFromPositionFile = TRUE;
\r
1235 CopyBoard(filePosition, boards[0]);
\r
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1239 if (initialMode == AnalyzeMode) {
\r
1240 if (appData.noChessProgram) {
\r
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1244 if (appData.icsActive) {
\r
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1248 AnalyzeModeEvent();
\r
1249 } else if (initialMode == AnalyzeFile) {
\r
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1251 ShowThinkingEvent();
\r
1252 AnalyzeFileEvent();
\r
1253 AnalysisPeriodicEvent(1);
\r
1254 } else if (initialMode == MachinePlaysWhite) {
\r
1255 if (appData.noChessProgram) {
\r
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1260 if (appData.icsActive) {
\r
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1265 MachineWhiteEvent();
\r
1266 } else if (initialMode == MachinePlaysBlack) {
\r
1267 if (appData.noChessProgram) {
\r
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1272 if (appData.icsActive) {
\r
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1277 MachineBlackEvent();
\r
1278 } else if (initialMode == TwoMachinesPlay) {
\r
1279 if (appData.noChessProgram) {
\r
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1284 if (appData.icsActive) {
\r
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1289 TwoMachinesEvent();
\r
1290 } else if (initialMode == EditGame) {
\r
1292 } else if (initialMode == EditPosition) {
\r
1293 EditPositionEvent();
\r
1294 } else if (initialMode == Training) {
\r
1295 if (*appData.loadGameFile == NULLCHAR) {
\r
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1305 * Establish will establish a contact to a remote host.port.
\r
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1307 * used to talk to the host.
\r
1308 * Returns 0 if okay, error code if not.
\r
1313 char buf[MSG_SIZ];
\r
1315 if (*appData.icsCommPort != NULLCHAR) {
\r
1316 /* Talk to the host through a serial comm port */
\r
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1319 } else if (*appData.gateway != NULLCHAR) {
\r
1320 if (*appData.remoteShell == NULLCHAR) {
\r
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1322 sprintf(buf, "%s %s %s",
\r
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1327 /* Use the rsh program to run telnet program on a gateway host */
\r
1328 if (*appData.remoteUser == NULLCHAR) {
\r
1329 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1330 appData.gateway, appData.telnetProgram,
\r
1331 appData.icsHost, appData.icsPort);
\r
1333 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1334 appData.remoteShell, appData.gateway,
\r
1335 appData.remoteUser, appData.telnetProgram,
\r
1336 appData.icsHost, appData.icsPort);
\r
1338 return StartChildProcess(buf, "", &icsPR);
\r
1341 } else if (appData.useTelnet) {
\r
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1345 /* TCP socket interface differs somewhat between
\r
1346 Unix and NT; handle details in the front end.
\r
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1353 show_bytes(fp, buf, count)
\r
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1360 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1369 /* Returns an errno value */
\r
1371 OutputMaybeTelnet(pr, message, count, outError)
\r
1377 char buf[8192], *p, *q, *buflim;
\r
1378 int left, newcount, outcount;
\r
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1381 *appData.gateway != NULLCHAR) {
\r
1382 if (appData.debugMode) {
\r
1383 fprintf(debugFP, ">ICS: ");
\r
1384 show_bytes(debugFP, message, count);
\r
1385 fprintf(debugFP, "\n");
\r
1387 return OutputToProcess(pr, message, count, outError);
\r
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1396 if (q >= buflim) {
\r
1397 if (appData.debugMode) {
\r
1398 fprintf(debugFP, ">ICS: ");
\r
1399 show_bytes(debugFP, buf, newcount);
\r
1400 fprintf(debugFP, "\n");
\r
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1403 if (outcount < newcount) return -1; /* to be sure */
\r
1410 } else if (((unsigned char) *p) == TN_IAC) {
\r
1411 *q++ = (char) TN_IAC;
\r
1418 if (appData.debugMode) {
\r
1419 fprintf(debugFP, ">ICS: ");
\r
1420 show_bytes(debugFP, buf, newcount);
\r
1421 fprintf(debugFP, "\n");
\r
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1424 if (outcount < newcount) return -1; /* to be sure */
\r
1429 read_from_player(isr, closure, message, count, error)
\r
1430 InputSourceRef isr;
\r
1436 int outError, outCount;
\r
1437 static int gotEof = 0;
\r
1439 /* Pass data read from player on to ICS */
\r
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1443 if (outCount < count) {
\r
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1446 } else if (count < 0) {
\r
1447 RemoveInputSource(isr);
\r
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1449 } else if (gotEof++ > 0) {
\r
1450 RemoveInputSource(isr);
\r
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1459 int count, outCount, outError;
\r
1461 if (icsPR == NULL) return;
\r
1463 count = strlen(s);
\r
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1465 if (outCount < count) {
\r
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1470 /* This is used for sending logon scripts to the ICS. Sending
\r
1471 without a delay causes problems when using timestamp on ICC
\r
1472 (at least on my machine). */
\r
1474 SendToICSDelayed(s,msdelay)
\r
1478 int count, outCount, outError;
\r
1480 if (icsPR == NULL) return;
\r
1482 count = strlen(s);
\r
1483 if (appData.debugMode) {
\r
1484 fprintf(debugFP, ">ICS: ");
\r
1485 show_bytes(debugFP, s, count);
\r
1486 fprintf(debugFP, "\n");
\r
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1490 if (outCount < count) {
\r
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1496 /* Remove all highlighting escape sequences in s
\r
1497 Also deletes any suffix starting with '('
\r
1500 StripHighlightAndTitle(s)
\r
1503 static char retbuf[MSG_SIZ];
\r
1506 while (*s != NULLCHAR) {
\r
1507 while (*s == '\033') {
\r
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1509 if (*s != NULLCHAR) s++;
\r
1511 while (*s != NULLCHAR && *s != '\033') {
\r
1512 if (*s == '(' || *s == '[') {
\r
1523 /* Remove all highlighting escape sequences in s */
\r
1528 static char retbuf[MSG_SIZ];
\r
1531 while (*s != NULLCHAR) {
\r
1532 while (*s == '\033') {
\r
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1534 if (*s != NULLCHAR) s++;
\r
1536 while (*s != NULLCHAR && *s != '\033') {
\r
1544 char *variantNames[] = VARIANT_NAMES;
\r
1549 return variantNames[v];
\r
1553 /* Identify a variant from the strings the chess servers use or the
\r
1554 PGN Variant tag names we use. */
\r
1556 StringToVariant(e)
\r
1561 VariantClass v = VariantNormal;
\r
1562 int i, found = FALSE;
\r
1563 char buf[MSG_SIZ];
\r
1567 /* [HGM] skip over optional board-size prefixes */
\r
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1570 while( *e++ != '_');
\r
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1574 if (StrCaseStr(e, variantNames[i])) {
\r
1575 v = (VariantClass) i;
\r
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1583 || StrCaseStr(e, "wild/fr")
\r
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1585 v = VariantFischeRandom;
\r
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1587 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1590 if (isdigit(*p)) {
\r
1596 case 0: /* FICS only, actually */
\r
1598 /* Castling legal even if K starts on d-file */
\r
1599 v = VariantWildCastle;
\r
1604 /* Castling illegal even if K & R happen to start in
\r
1605 normal positions. */
\r
1606 v = VariantNoCastle;
\r
1619 /* Castling legal iff K & R start in normal positions */
\r
1620 v = VariantNormal;
\r
1625 /* Special wilds for position setup; unclear what to do here */
\r
1626 v = VariantLoadable;
\r
1629 /* Bizarre ICC game */
\r
1630 v = VariantTwoKings;
\r
1633 v = VariantKriegspiel;
\r
1636 v = VariantLosers;
\r
1639 v = VariantFischeRandom;
\r
1642 v = VariantCrazyhouse;
\r
1645 v = VariantBughouse;
\r
1648 v = Variant3Check;
\r
1651 /* Not quite the same as FICS suicide! */
\r
1652 v = VariantGiveaway;
\r
1655 v = VariantAtomic;
\r
1658 v = VariantShatranj;
\r
1661 /* Temporary names for future ICC types. The name *will* change in
\r
1662 the next xboard/WinBoard release after ICC defines it. */
\r
1691 v = VariantXiangqi;
\r
1694 v = VariantCourier;
\r
1697 v = VariantGothic;
\r
1700 v = VariantCapablanca;
\r
1703 v = VariantKnightmate;
\r
1709 v = VariantCylinder;
\r
1712 v = VariantFalcon;
\r
1715 v = VariantCapaRandom;
\r
1718 v = VariantBerolina;
\r
1730 /* Found "wild" or "w" in the string but no number;
\r
1731 must assume it's normal chess. */
\r
1732 v = VariantNormal;
\r
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1736 DisplayError(buf, 0);
\r
1737 v = VariantUnknown;
\r
1742 if (appData.debugMode) {
\r
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1744 e, wnum, VariantName(v));
\r
1749 static int leftover_start = 0, leftover_len = 0;
\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1753 advance *index beyond it, and set leftover_start to the new value of
\r
1754 *index; else return FALSE. If pattern contains the character '*', it
\r
1755 matches any sequence of characters not containing '\r', '\n', or the
\r
1756 character following the '*' (if any), and the matched sequence(s) are
\r
1757 copied into star_match.
\r
1760 looking_at(buf, index, pattern)
\r
1765 char *bufp = &buf[*index], *patternp = pattern;
\r
1766 int star_count = 0;
\r
1767 char *matchp = star_match[0];
\r
1770 if (*patternp == NULLCHAR) {
\r
1771 *index = leftover_start = bufp - buf;
\r
1772 *matchp = NULLCHAR;
\r
1775 if (*bufp == NULLCHAR) return FALSE;
\r
1776 if (*patternp == '*') {
\r
1777 if (*bufp == *(patternp + 1)) {
\r
1778 *matchp = NULLCHAR;
\r
1779 matchp = star_match[++star_count];
\r
1783 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1785 if (*patternp == NULLCHAR)
\r
1790 *matchp++ = *bufp++;
\r
1794 if (*patternp != *bufp) return FALSE;
\r
1801 SendToPlayer(data, length)
\r
1805 int error, outCount;
\r
1806 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1807 if (outCount < length) {
\r
1808 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1813 PackHolding(packed, holding)
\r
1817 char *p = holding;
\r
1819 int runlength = 0;
\r
1825 switch (runlength) {
\r
1836 sprintf(q, "%d", runlength);
\r
1848 /* Telnet protocol requests from the front end */
\r
1850 TelnetRequest(ddww, option)
\r
1851 unsigned char ddww, option;
\r
1853 unsigned char msg[3];
\r
1854 int outCount, outError;
\r
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1858 if (appData.debugMode) {
\r
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1875 sprintf(buf1, "%d", ddww);
\r
1880 optionStr = "ECHO";
\r
1884 sprintf(buf2, "%d", option);
\r
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1893 if (outCount < 3) {
\r
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1901 if (!appData.icsActive) return;
\r
1902 TelnetRequest(TN_DO, TN_ECHO);
\r
1908 if (!appData.icsActive) return;
\r
1909 TelnetRequest(TN_DONT, TN_ECHO);
\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1915 /* put the holdings sent to us by the server on the board holdings area */
\r
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1918 ChessSquare piece;
\r
1920 if(gameInfo.holdingsWidth < 2) return;
\r
1922 if( (int)lowestPiece >= BlackPawn ) {
\r
1923 holdingsColumn = 0;
\r
1925 holdingsStartRow = BOARD_HEIGHT-1;
\r
1928 holdingsColumn = BOARD_WIDTH-1;
\r
1929 countsColumn = BOARD_WIDTH-2;
\r
1930 holdingsStartRow = 0;
\r
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1935 board[i][holdingsColumn] = EmptySquare;
\r
1936 board[i][countsColumn] = (ChessSquare) 0;
\r
1938 while( (p=*holdings++) != NULLCHAR ) {
\r
1939 piece = CharToPiece( ToUpper(p) );
\r
1940 if(piece == EmptySquare) continue;
\r
1941 /*j = (int) piece - (int) WhitePawn;*/
\r
1942 j = PieceToNumber(piece);
\r
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1944 if(j < 0) continue; /* should not happen */
\r
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1947 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1954 VariantSwitch(Board board, VariantClass newVariant)
\r
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1960 startedFromPositionFile = FALSE;
\r
1961 if(gameInfo.variant == newVariant) return;
\r
1963 /* [HGM] This routine is called each time an assignment is made to
\r
1964 * gameInfo.variant during a game, to make sure the board sizes
\r
1965 * are set to match the new variant. If that means adding or deleting
\r
1966 * holdings, we shift the playing board accordingly
\r
1967 * This kludge is needed because in ICS observe mode, we get boards
\r
1968 * of an ongoing game without knowing the variant, and learn about the
\r
1969 * latter only later. This can be because of the move list we requested,
\r
1970 * in which case the game history is refilled from the beginning anyway,
\r
1971 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1972 * case we want to add those holdings to the already received position.
\r
1976 if (appData.debugMode) {
\r
1977 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1978 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1979 setbuf(debugFP, NULL);
\r
1981 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1983 switch(newVariant) {
\r
1984 case VariantShogi:
\r
1985 newWidth = 9; newHeight = 9;
\r
1986 gameInfo.holdingsSize = 7;
\r
1987 case VariantBughouse:
\r
1988 case VariantCrazyhouse:
\r
1989 newHoldingsWidth = 2; break;
\r
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1994 if(newWidth != gameInfo.boardWidth ||
\r
1995 newHeight != gameInfo.boardHeight ||
\r
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1998 /* shift position to new playing area, if needed */
\r
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
2000 for(i=0; i<BOARD_HEIGHT; i++)
\r
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2004 for(i=0; i<newHeight; i++) {
\r
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2009 for(i=0; i<BOARD_HEIGHT; i++)
\r
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2015 gameInfo.boardWidth = newWidth;
\r
2016 gameInfo.boardHeight = newHeight;
\r
2017 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2018 gameInfo.variant = newVariant;
\r
2019 InitDrawingSizes(-2, 0);
\r
2021 /* [HGM] The following should definitely be solved in a better way */
\r
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2025 saveEP = epStatus[0];
\r
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2029 epStatus[0] = saveEP;
\r
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2035 forwardMostMove = oldForwardMostMove;
\r
2036 backwardMostMove = oldBackwardMostMove;
\r
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2040 static int loggedOn = FALSE;
\r
2042 /*-- Game start info cache: --*/
\r
2044 char gs_kind[MSG_SIZ];
\r
2045 static char player1Name[128] = "";
\r
2046 static char player2Name[128] = "";
\r
2047 static int player1Rating = -1;
\r
2048 static int player2Rating = -1;
\r
2049 /*----------------------------*/
\r
2051 ColorClass curColor = ColorNormal;
\r
2052 int suppressKibitz = 0;
\r
2055 read_from_ics(isr, closure, data, count, error)
\r
2056 InputSourceRef isr;
\r
2062 #define BUF_SIZE 8192
\r
2063 #define STARTED_NONE 0
\r
2064 #define STARTED_MOVES 1
\r
2065 #define STARTED_BOARD 2
\r
2066 #define STARTED_OBSERVE 3
\r
2067 #define STARTED_HOLDINGS 4
\r
2068 #define STARTED_CHATTER 5
\r
2069 #define STARTED_COMMENT 6
\r
2070 #define STARTED_MOVES_NOHIDE 7
\r
2072 static int started = STARTED_NONE;
\r
2073 static char parse[20000];
\r
2074 static int parse_pos = 0;
\r
2075 static char buf[BUF_SIZE + 1];
\r
2076 static int firstTime = TRUE, intfSet = FALSE;
\r
2077 static ColorClass prevColor = ColorNormal;
\r
2078 static int savingComment = FALSE;
\r
2084 int backup; /* [DM] For zippy color lines */
\r
2087 if (appData.debugMode) {
\r
2089 fprintf(debugFP, "<ICS: ");
\r
2090 show_bytes(debugFP, data, count);
\r
2091 fprintf(debugFP, "\n");
\r
2095 if (appData.debugMode) { int f = forwardMostMove;
\r
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2100 /* If last read ended with a partial line that we couldn't parse,
\r
2101 prepend it to the new read and try again. */
\r
2102 if (leftover_len > 0) {
\r
2103 for (i=0; i<leftover_len; i++)
\r
2104 buf[i] = buf[leftover_start + i];
\r
2107 /* Copy in new characters, removing nulls and \r's */
\r
2108 buf_len = leftover_len;
\r
2109 for (i = 0; i < count; i++) {
\r
2110 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2111 buf[buf_len++] = data[i];
\r
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2117 buf[buf_len] = NULLCHAR;
\r
2118 next_out = leftover_len;
\r
2119 leftover_start = 0;
\r
2122 while (i < buf_len) {
\r
2123 /* Deal with part of the TELNET option negotiation
\r
2124 protocol. We refuse to do anything beyond the
\r
2125 defaults, except that we allow the WILL ECHO option,
\r
2126 which ICS uses to turn off password echoing when we are
\r
2127 directly connected to it. We reject this option
\r
2128 if localLineEditing mode is on (always on in xboard)
\r
2129 and we are talking to port 23, which might be a real
\r
2130 telnet server that will try to keep WILL ECHO on permanently.
\r
2132 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2133 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2134 unsigned char option;
\r
2136 switch ((unsigned char) buf[++i]) {
\r
2138 if (appData.debugMode)
\r
2139 fprintf(debugFP, "\n<WILL ");
\r
2140 switch (option = (unsigned char) buf[++i]) {
\r
2142 if (appData.debugMode)
\r
2143 fprintf(debugFP, "ECHO ");
\r
2144 /* Reply only if this is a change, according
\r
2145 to the protocol rules. */
\r
2146 if (remoteEchoOption) break;
\r
2147 if (appData.localLineEditing &&
\r
2148 atoi(appData.icsPort) == TN_PORT) {
\r
2149 TelnetRequest(TN_DONT, TN_ECHO);
\r
2152 TelnetRequest(TN_DO, TN_ECHO);
\r
2153 remoteEchoOption = TRUE;
\r
2157 if (appData.debugMode)
\r
2158 fprintf(debugFP, "%d ", option);
\r
2159 /* Whatever this is, we don't want it. */
\r
2160 TelnetRequest(TN_DONT, option);
\r
2165 if (appData.debugMode)
\r
2166 fprintf(debugFP, "\n<WONT ");
\r
2167 switch (option = (unsigned char) buf[++i]) {
\r
2169 if (appData.debugMode)
\r
2170 fprintf(debugFP, "ECHO ");
\r
2171 /* Reply only if this is a change, according
\r
2172 to the protocol rules. */
\r
2173 if (!remoteEchoOption) break;
\r
2175 TelnetRequest(TN_DONT, TN_ECHO);
\r
2176 remoteEchoOption = FALSE;
\r
2179 if (appData.debugMode)
\r
2180 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2181 /* Whatever this is, it must already be turned
\r
2182 off, because we never agree to turn on
\r
2183 anything non-default, so according to the
\r
2184 protocol rules, we don't reply. */
\r
2189 if (appData.debugMode)
\r
2190 fprintf(debugFP, "\n<DO ");
\r
2191 switch (option = (unsigned char) buf[++i]) {
\r
2193 /* Whatever this is, we refuse to do it. */
\r
2194 if (appData.debugMode)
\r
2195 fprintf(debugFP, "%d ", option);
\r
2196 TelnetRequest(TN_WONT, option);
\r
2201 if (appData.debugMode)
\r
2202 fprintf(debugFP, "\n<DONT ");
\r
2203 switch (option = (unsigned char) buf[++i]) {
\r
2205 if (appData.debugMode)
\r
2206 fprintf(debugFP, "%d ", option);
\r
2207 /* Whatever this is, we are already not doing
\r
2208 it, because we never agree to do anything
\r
2209 non-default, so according to the protocol
\r
2210 rules, we don't reply. */
\r
2215 if (appData.debugMode)
\r
2216 fprintf(debugFP, "\n<IAC ");
\r
2217 /* Doubled IAC; pass it through */
\r
2221 if (appData.debugMode)
\r
2222 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2223 /* Drop all other telnet commands on the floor */
\r
2226 if (oldi > next_out)
\r
2227 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2228 if (++i > next_out)
\r
2233 /* OK, this at least will *usually* work */
\r
2234 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2238 if (loggedOn && !intfSet) {
\r
2239 if (ics_type == ICS_ICC) {
\r
2241 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2244 } else if (ics_type == ICS_CHESSNET) {
\r
2245 sprintf(str, "/style 12\n");
\r
2247 strcpy(str, "alias $ @\n$set interface ");
\r
2248 strcat(str, programVersion);
\r
2249 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2251 strcat(str, "$iset nohighlight 1\n");
\r
2253 strcat(str, "$iset lock 1\n$style 12\n");
\r
2259 if (started == STARTED_COMMENT) {
\r
2260 /* Accumulate characters in comment */
\r
2261 parse[parse_pos++] = buf[i];
\r
2262 if (buf[i] == '\n') {
\r
2263 parse[parse_pos] = NULLCHAR;
\r
2264 if(!suppressKibitz) // [HGM] kibitz
\r
2265 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2266 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2267 int nrDigit = 0, nrAlph = 0, i;
\r
2268 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2269 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2270 parse[parse_pos] = NULLCHAR;
\r
2271 // try to be smart: if it does not look like search info, it should go to
\r
2272 // ICS interaction window after all, not to engine-output window.
\r
2273 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2274 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2275 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2276 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2278 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2279 OutputKibitz(suppressKibitz, parse);
\r
2281 char tmp[MSG_SIZ];
\r
2282 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2283 SendToPlayer(tmp, strlen(tmp));
\r
2286 started = STARTED_NONE;
\r
2288 /* Don't match patterns against characters in chatter */
\r
2293 if (started == STARTED_CHATTER) {
\r
2294 if (buf[i] != '\n') {
\r
2295 /* Don't match patterns against characters in chatter */
\r
2299 started = STARTED_NONE;
\r
2302 /* Kludge to deal with rcmd protocol */
\r
2303 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2304 DisplayFatalError(&buf[1], 0, 1);
\r
2307 firstTime = FALSE;
\r
2310 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2311 ics_type = ICS_ICC;
\r
2313 if (appData.debugMode)
\r
2314 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2317 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2318 ics_type = ICS_FICS;
\r
2320 if (appData.debugMode)
\r
2321 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2324 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2325 ics_type = ICS_CHESSNET;
\r
2327 if (appData.debugMode)
\r
2328 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2333 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2334 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2335 looking_at(buf, &i, "will be \"*\""))) {
\r
2336 strcpy(ics_handle, star_match[0]);
\r
2340 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2341 char buf[MSG_SIZ];
\r
2342 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2343 DisplayIcsInteractionTitle(buf);
\r
2344 have_set_title = TRUE;
\r
2347 /* skip finger notes */
\r
2348 if (started == STARTED_NONE &&
\r
2349 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2350 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2351 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2352 started = STARTED_CHATTER;
\r
2357 /* skip formula vars */
\r
2358 if (started == STARTED_NONE &&
\r
2359 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2360 started = STARTED_CHATTER;
\r
2366 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2367 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2368 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2369 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2370 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2371 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2372 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2373 suppressKibitz = TRUE;
\r
2374 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2375 && (gameMode == IcsPlayingWhite)) ||
\r
2376 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2377 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2378 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2380 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2381 parse_pos = 0; parse[0] = NULLCHAR;
\r
2382 savingComment = TRUE;
\r
2383 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2384 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2388 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2389 started = STARTED_CHATTER;
\r
2390 suppressKibitz = TRUE;
\r
2392 } // [HGM] kibitz: end of patch
\r
2394 if (appData.zippyTalk || appData.zippyPlay) {
\r
2395 /* [DM] Backup address for color zippy lines */
\r
2399 if (loggedOn == TRUE)
\r
2400 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2401 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2403 if (ZippyControl(buf, &i) ||
\r
2404 ZippyConverse(buf, &i) ||
\r
2405 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2407 if (!appData.colorize) continue;
\r
2411 } // [DM] 'else { ' deleted
\r
2412 if (/* Don't color "message" or "messages" output */
\r
2413 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2414 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2415 looking_at(buf, &i, "--* (*:*): ") ||
\r
2416 /* Regular tells and says */
\r
2417 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2418 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2419 looking_at(buf, &i, "* says: ") ||
\r
2420 /* Message notifications (same color as tells) */
\r
2421 looking_at(buf, &i, "* has left a message ") ||
\r
2422 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2423 /* Whispers and kibitzes */
\r
2424 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2425 looking_at(buf, &i, "* kibitzes: ") ||
\r
2426 /* Channel tells */
\r
2427 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2429 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2430 /* Avoid "tells you:" spoofs in channels */
\r
2433 if (star_match[0][0] == NULLCHAR ||
\r
2434 strchr(star_match[0], ' ') ||
\r
2435 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2436 /* Reject bogus matches */
\r
2439 if (appData.colorize) {
\r
2440 if (oldi > next_out) {
\r
2441 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2446 Colorize(ColorTell, FALSE);
\r
2447 curColor = ColorTell;
\r
2450 Colorize(ColorKibitz, FALSE);
\r
2451 curColor = ColorKibitz;
\r
2454 p = strrchr(star_match[1], '(');
\r
2456 p = star_match[1];
\r
2460 if (atoi(p) == 1) {
\r
2461 Colorize(ColorChannel1, FALSE);
\r
2462 curColor = ColorChannel1;
\r
2464 Colorize(ColorChannel, FALSE);
\r
2465 curColor = ColorChannel;
\r
2469 curColor = ColorNormal;
\r
2473 if (started == STARTED_NONE && appData.autoComment &&
\r
2474 (gameMode == IcsObserving ||
\r
2475 gameMode == IcsPlayingWhite ||
\r
2476 gameMode == IcsPlayingBlack)) {
\r
2477 parse_pos = i - oldi;
\r
2478 memcpy(parse, &buf[oldi], parse_pos);
\r
2479 parse[parse_pos] = NULLCHAR;
\r
2480 started = STARTED_COMMENT;
\r
2481 savingComment = TRUE;
\r
2483 started = STARTED_CHATTER;
\r
2484 savingComment = FALSE;
\r
2491 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2492 looking_at(buf, &i, "* c-shouts: ")) {
\r
2493 if (appData.colorize) {
\r
2494 if (oldi > next_out) {
\r
2495 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2498 Colorize(ColorSShout, FALSE);
\r
2499 curColor = ColorSShout;
\r
2502 started = STARTED_CHATTER;
\r
2506 if (looking_at(buf, &i, "--->")) {
\r
2511 if (looking_at(buf, &i, "* shouts: ") ||
\r
2512 looking_at(buf, &i, "--> ")) {
\r
2513 if (appData.colorize) {
\r
2514 if (oldi > next_out) {
\r
2515 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2518 Colorize(ColorShout, FALSE);
\r
2519 curColor = ColorShout;
\r
2522 started = STARTED_CHATTER;
\r
2526 if (looking_at( buf, &i, "Challenge:")) {
\r
2527 if (appData.colorize) {
\r
2528 if (oldi > next_out) {
\r
2529 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2532 Colorize(ColorChallenge, FALSE);
\r
2533 curColor = ColorChallenge;
\r
2539 if (looking_at(buf, &i, "* offers you") ||
\r
2540 looking_at(buf, &i, "* offers to be") ||
\r
2541 looking_at(buf, &i, "* would like to") ||
\r
2542 looking_at(buf, &i, "* requests to") ||
\r
2543 looking_at(buf, &i, "Your opponent offers") ||
\r
2544 looking_at(buf, &i, "Your opponent requests")) {
\r
2546 if (appData.colorize) {
\r
2547 if (oldi > next_out) {
\r
2548 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2551 Colorize(ColorRequest, FALSE);
\r
2552 curColor = ColorRequest;
\r
2557 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2558 if (appData.colorize) {
\r
2559 if (oldi > next_out) {
\r
2560 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2563 Colorize(ColorSeek, FALSE);
\r
2564 curColor = ColorSeek;
\r
2569 if (looking_at(buf, &i, "\\ ")) {
\r
2570 if (prevColor != ColorNormal) {
\r
2571 if (oldi > next_out) {
\r
2572 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2575 Colorize(prevColor, TRUE);
\r
2576 curColor = prevColor;
\r
2578 if (savingComment) {
\r
2579 parse_pos = i - oldi;
\r
2580 memcpy(parse, &buf[oldi], parse_pos);
\r
2581 parse[parse_pos] = NULLCHAR;
\r
2582 started = STARTED_COMMENT;
\r
2584 started = STARTED_CHATTER;
\r
2589 if (looking_at(buf, &i, "Black Strength :") ||
\r
2590 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2591 looking_at(buf, &i, "<10>") ||
\r
2592 looking_at(buf, &i, "#@#")) {
\r
2593 /* Wrong board style */
\r
2595 SendToICS(ics_prefix);
\r
2596 SendToICS("set style 12\n");
\r
2597 SendToICS(ics_prefix);
\r
2598 SendToICS("refresh\n");
\r
2602 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2604 have_sent_ICS_logon = 1;
\r
2608 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2609 (looking_at(buf, &i, "\n<12> ") ||
\r
2610 looking_at(buf, &i, "<12> "))) {
\r
2612 if (oldi > next_out) {
\r
2613 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2616 started = STARTED_BOARD;
\r
2621 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2622 looking_at(buf, &i, "<b1> ")) {
\r
2623 if (oldi > next_out) {
\r
2624 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2627 started = STARTED_HOLDINGS;
\r
2632 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2634 /* Header for a move list -- first line */
\r
2636 switch (ics_getting_history) {
\r
2638 switch (gameMode) {
\r
2640 case BeginningOfGame:
\r
2641 /* User typed "moves" or "oldmoves" while we
\r
2642 were idle. Pretend we asked for these
\r
2643 moves and soak them up so user can step
\r
2644 through them and/or save them.
\r
2646 Reset(FALSE, TRUE);
\r
2647 gameMode = IcsObserving;
\r
2650 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2652 case EditGame: /*?*/
\r
2653 case EditPosition: /*?*/
\r
2654 /* Should above feature work in these modes too? */
\r
2655 /* For now it doesn't */
\r
2656 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2659 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2664 /* Is this the right one? */
\r
2665 if (gameInfo.white && gameInfo.black &&
\r
2666 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2667 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2669 ics_getting_history = H_GOT_REQ_HEADER;
\r
2672 case H_GOT_REQ_HEADER:
\r
2673 case H_GOT_UNREQ_HEADER:
\r
2674 case H_GOT_UNWANTED_HEADER:
\r
2675 case H_GETTING_MOVES:
\r
2676 /* Should not happen */
\r
2677 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2678 ics_getting_history = H_FALSE;
\r
2682 /* Save player ratings into gameInfo if needed */
\r
2683 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2684 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2685 (gameInfo.whiteRating == -1 ||
\r
2686 gameInfo.blackRating == -1)) {
\r
2688 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2689 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2690 if (appData.debugMode)
\r
2691 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2692 gameInfo.whiteRating, gameInfo.blackRating);
\r
2697 if (looking_at(buf, &i,
\r
2698 "* * match, initial time: * minute*, increment: * second")) {
\r
2699 /* Header for a move list -- second line */
\r
2700 /* Initial board will follow if this is a wild game */
\r
2701 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2702 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2703 gameInfo.event = StrSave(str);
\r
2704 /* [HGM] we switched variant. Translate boards if needed. */
\r
2705 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2709 if (looking_at(buf, &i, "Move ")) {
\r
2710 /* Beginning of a move list */
\r
2711 switch (ics_getting_history) {
\r
2713 /* Normally should not happen */
\r
2714 /* Maybe user hit reset while we were parsing */
\r
2717 /* Happens if we are ignoring a move list that is not
\r
2718 * the one we just requested. Common if the user
\r
2719 * tries to observe two games without turning off
\r
2722 case H_GETTING_MOVES:
\r
2723 /* Should not happen */
\r
2724 DisplayError(_("Error gathering move list: nested"), 0);
\r
2725 ics_getting_history = H_FALSE;
\r
2727 case H_GOT_REQ_HEADER:
\r
2728 ics_getting_history = H_GETTING_MOVES;
\r
2729 started = STARTED_MOVES;
\r
2731 if (oldi > next_out) {
\r
2732 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2735 case H_GOT_UNREQ_HEADER:
\r
2736 ics_getting_history = H_GETTING_MOVES;
\r
2737 started = STARTED_MOVES_NOHIDE;
\r
2740 case H_GOT_UNWANTED_HEADER:
\r
2741 ics_getting_history = H_FALSE;
\r
2747 if (looking_at(buf, &i, "% ") ||
\r
2748 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2749 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2750 savingComment = FALSE;
\r
2751 switch (started) {
\r
2752 case STARTED_MOVES:
\r
2753 case STARTED_MOVES_NOHIDE:
\r
2754 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2755 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2756 ParseGameHistory(parse);
\r
2758 if (appData.zippyPlay && first.initDone) {
\r
2759 FeedMovesToProgram(&first, forwardMostMove);
\r
2760 if (gameMode == IcsPlayingWhite) {
\r
2761 if (WhiteOnMove(forwardMostMove)) {
\r
2762 if (first.sendTime) {
\r
2763 if (first.useColors) {
\r
2764 SendToProgram("black\n", &first);
\r
2766 SendTimeRemaining(&first, TRUE);
\r
2769 if (first.useColors) {
\r
2770 SendToProgram("white\ngo\n", &first);
\r
2772 SendToProgram("go\n", &first);
\r
2775 if (first.useColors) {
\r
2776 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2778 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2780 first.maybeThinking = TRUE;
\r
2782 if (first.usePlayother) {
\r
2783 if (first.sendTime) {
\r
2784 SendTimeRemaining(&first, TRUE);
\r
2786 SendToProgram("playother\n", &first);
\r
2787 firstMove = FALSE;
\r
2792 } else if (gameMode == IcsPlayingBlack) {
\r
2793 if (!WhiteOnMove(forwardMostMove)) {
\r
2794 if (first.sendTime) {
\r
2795 if (first.useColors) {
\r
2796 SendToProgram("white\n", &first);
\r
2798 SendTimeRemaining(&first, FALSE);
\r
2801 if (first.useColors) {
\r
2802 SendToProgram("black\ngo\n", &first);
\r
2804 SendToProgram("go\n", &first);
\r
2807 if (first.useColors) {
\r
2808 SendToProgram("black\n", &first);
\r
2810 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2812 first.maybeThinking = TRUE;
\r
2814 if (first.usePlayother) {
\r
2815 if (first.sendTime) {
\r
2816 SendTimeRemaining(&first, FALSE);
\r
2818 SendToProgram("playother\n", &first);
\r
2819 firstMove = FALSE;
\r
2827 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2828 /* Moves came from oldmoves or moves command
\r
2829 while we weren't doing anything else.
\r
2831 currentMove = forwardMostMove;
\r
2832 ClearHighlights();/*!!could figure this out*/
\r
2833 flipView = appData.flipView;
\r
2834 DrawPosition(FALSE, boards[currentMove]);
\r
2835 DisplayBothClocks();
\r
2836 sprintf(str, "%s vs. %s",
\r
2837 gameInfo.white, gameInfo.black);
\r
2838 DisplayTitle(str);
\r
2839 gameMode = IcsIdle;
\r
2841 /* Moves were history of an active game */
\r
2842 if (gameInfo.resultDetails != NULL) {
\r
2843 free(gameInfo.resultDetails);
\r
2844 gameInfo.resultDetails = NULL;
\r
2847 HistorySet(parseList, backwardMostMove,
\r
2848 forwardMostMove, currentMove-1);
\r
2849 DisplayMove(currentMove - 1);
\r
2850 if (started == STARTED_MOVES) next_out = i;
\r
2851 started = STARTED_NONE;
\r
2852 ics_getting_history = H_FALSE;
\r
2855 case STARTED_OBSERVE:
\r
2856 started = STARTED_NONE;
\r
2857 SendToICS(ics_prefix);
\r
2858 SendToICS("refresh\n");
\r
2864 if(bookHit) { // [HGM] book: simulate book reply
\r
2865 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2867 programStats.nodes = programStats.depth = programStats.time =
\r
2868 programStats.score = programStats.got_only_move = 0;
\r
2869 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2871 strcpy(bookMove, "move ");
\r
2872 strcat(bookMove, bookHit);
\r
2873 HandleMachineMove(bookMove, &first);
\r
2878 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2879 started == STARTED_HOLDINGS ||
\r
2880 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2881 /* Accumulate characters in move list or board */
\r
2882 parse[parse_pos++] = buf[i];
\r
2885 /* Start of game messages. Mostly we detect start of game
\r
2886 when the first board image arrives. On some versions
\r
2887 of the ICS, though, we need to do a "refresh" after starting
\r
2888 to observe in order to get the current board right away. */
\r
2889 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2890 started = STARTED_OBSERVE;
\r
2894 /* Handle auto-observe */
\r
2895 if (appData.autoObserve &&
\r
2896 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2897 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2899 /* Choose the player that was highlighted, if any. */
\r
2900 if (star_match[0][0] == '\033' ||
\r
2901 star_match[1][0] != '\033') {
\r
2902 player = star_match[0];
\r
2904 player = star_match[2];
\r
2906 sprintf(str, "%sobserve %s\n",
\r
2907 ics_prefix, StripHighlightAndTitle(player));
\r
2910 /* Save ratings from notify string */
\r
2911 strcpy(player1Name, star_match[0]);
\r
2912 player1Rating = string_to_rating(star_match[1]);
\r
2913 strcpy(player2Name, star_match[2]);
\r
2914 player2Rating = string_to_rating(star_match[3]);
\r
2916 if (appData.debugMode)
\r
2918 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2919 player1Name, player1Rating,
\r
2920 player2Name, player2Rating);
\r
2925 /* Deal with automatic examine mode after a game,
\r
2926 and with IcsObserving -> IcsExamining transition */
\r
2927 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2928 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2930 int gamenum = atoi(star_match[0]);
\r
2931 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2932 gamenum == ics_gamenum) {
\r
2933 /* We were already playing or observing this game;
\r
2934 no need to refetch history */
\r
2935 gameMode = IcsExamining;
\r
2937 pauseExamForwardMostMove = forwardMostMove;
\r
2938 } else if (currentMove < forwardMostMove) {
\r
2939 ForwardInner(forwardMostMove);
\r
2942 /* I don't think this case really can happen */
\r
2943 SendToICS(ics_prefix);
\r
2944 SendToICS("refresh\n");
\r
2949 /* Error messages */
\r
2950 if (ics_user_moved) {
\r
2951 if (looking_at(buf, &i, "Illegal move") ||
\r
2952 looking_at(buf, &i, "Not a legal move") ||
\r
2953 looking_at(buf, &i, "Your king is in check") ||
\r
2954 looking_at(buf, &i, "It isn't your turn") ||
\r
2955 looking_at(buf, &i, "It is not your move")) {
\r
2956 /* Illegal move */
\r
2957 ics_user_moved = 0;
\r
2958 if (forwardMostMove > backwardMostMove) {
\r
2959 currentMove = --forwardMostMove;
\r
2960 DisplayMove(currentMove - 1); /* before DMError */
\r
2961 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2962 DrawPosition(FALSE, boards[currentMove]);
\r
2964 DisplayBothClocks();
\r
2970 if (looking_at(buf, &i, "still have time") ||
\r
2971 looking_at(buf, &i, "not out of time") ||
\r
2972 looking_at(buf, &i, "either player is out of time") ||
\r
2973 looking_at(buf, &i, "has timeseal; checking")) {
\r
2974 /* We must have called his flag a little too soon */
\r
2975 whiteFlag = blackFlag = FALSE;
\r
2979 if (looking_at(buf, &i, "added * seconds to") ||
\r
2980 looking_at(buf, &i, "seconds were added to")) {
\r
2981 /* Update the clocks */
\r
2982 SendToICS(ics_prefix);
\r
2983 SendToICS("refresh\n");
\r
2987 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2988 ics_clock_paused = TRUE;
\r
2993 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2994 ics_clock_paused = FALSE;
\r
2999 /* Grab player ratings from the Creating: message.
\r
3000 Note we have to check for the special case when
\r
3001 the ICS inserts things like [white] or [black]. */
\r
3002 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
3003 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3005 0 player 1 name (not necessarily white)
\r
3007 2 empty, white, or black (IGNORED)
\r
3008 3 player 2 name (not necessarily black)
\r
3011 The names/ratings are sorted out when the game
\r
3012 actually starts (below).
\r
3014 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3015 player1Rating = string_to_rating(star_match[1]);
\r
3016 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3017 player2Rating = string_to_rating(star_match[4]);
\r
3019 if (appData.debugMode)
\r
3021 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3022 player1Name, player1Rating,
\r
3023 player2Name, player2Rating);
\r
3028 /* Improved generic start/end-of-game messages */
\r
3029 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3030 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3031 /* If tkind == 0: */
\r
3032 /* star_match[0] is the game number */
\r
3033 /* [1] is the white player's name */
\r
3034 /* [2] is the black player's name */
\r
3035 /* For end-of-game: */
\r
3036 /* [3] is the reason for the game end */
\r
3037 /* [4] is a PGN end game-token, preceded by " " */
\r
3038 /* For start-of-game: */
\r
3039 /* [3] begins with "Creating" or "Continuing" */
\r
3040 /* [4] is " *" or empty (don't care). */
\r
3041 int gamenum = atoi(star_match[0]);
\r
3042 char *whitename, *blackname, *why, *endtoken;
\r
3043 ChessMove endtype = (ChessMove) 0;
\r
3046 whitename = star_match[1];
\r
3047 blackname = star_match[2];
\r
3048 why = star_match[3];
\r
3049 endtoken = star_match[4];
\r
3051 whitename = star_match[1];
\r
3052 blackname = star_match[3];
\r
3053 why = star_match[5];
\r
3054 endtoken = star_match[6];
\r
3057 /* Game start messages */
\r
3058 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3059 strncmp(why, "Continuing ", 11) == 0) {
\r
3060 gs_gamenum = gamenum;
\r
3061 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3063 if (appData.zippyPlay) {
\r
3064 ZippyGameStart(whitename, blackname);
\r
3070 /* Game end messages */
\r
3071 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3072 ics_gamenum != gamenum) {
\r
3075 while (endtoken[0] == ' ') endtoken++;
\r
3076 switch (endtoken[0]) {
\r
3079 endtype = GameUnfinished;
\r
3082 endtype = BlackWins;
\r
3085 if (endtoken[1] == '/')
\r
3086 endtype = GameIsDrawn;
\r
3088 endtype = WhiteWins;
\r
3091 GameEnds(endtype, why, GE_ICS);
\r
3093 if (appData.zippyPlay && first.initDone) {
\r
3094 ZippyGameEnd(endtype, why);
\r
3095 if (first.pr == NULL) {
\r
3096 /* Start the next process early so that we'll
\r
3097 be ready for the next challenge */
\r
3098 StartChessProgram(&first);
\r
3100 /* Send "new" early, in case this command takes
\r
3101 a long time to finish, so that we'll be ready
\r
3102 for the next challenge. */
\r
3103 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3104 Reset(TRUE, TRUE);
\r
3110 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3111 looking_at(buf, &i, "no longer observing game *") ||
\r
3112 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3113 if (gameMode == IcsObserving &&
\r
3114 atoi(star_match[0]) == ics_gamenum)
\r
3116 /* icsEngineAnalyze */
\r
3117 if (appData.icsEngineAnalyze) {
\r
3118 ExitAnalyzeMode();
\r
3122 gameMode = IcsIdle;
\r
3124 ics_user_moved = FALSE;
\r
3129 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3130 if (gameMode == IcsExamining &&
\r
3131 atoi(star_match[0]) == ics_gamenum)
\r
3133 gameMode = IcsIdle;
\r
3135 ics_user_moved = FALSE;
\r
3140 /* Advance leftover_start past any newlines we find,
\r
3141 so only partial lines can get reparsed */
\r
3142 if (looking_at(buf, &i, "\n")) {
\r
3143 prevColor = curColor;
\r
3144 if (curColor != ColorNormal) {
\r
3145 if (oldi > next_out) {
\r
3146 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3149 Colorize(ColorNormal, FALSE);
\r
3150 curColor = ColorNormal;
\r
3152 if (started == STARTED_BOARD) {
\r
3153 started = STARTED_NONE;
\r
3154 parse[parse_pos] = NULLCHAR;
\r
3155 ParseBoard12(parse);
\r
3156 ics_user_moved = 0;
\r
3158 /* Send premove here */
\r
3159 if (appData.premove) {
\r
3160 char str[MSG_SIZ];
\r
3161 if (currentMove == 0 &&
\r
3162 gameMode == IcsPlayingWhite &&
\r
3163 appData.premoveWhite) {
\r
3164 sprintf(str, "%s%s\n", ics_prefix,
\r
3165 appData.premoveWhiteText);
\r
3166 if (appData.debugMode)
\r
3167 fprintf(debugFP, "Sending premove:\n");
\r
3169 } else if (currentMove == 1 &&
\r
3170 gameMode == IcsPlayingBlack &&
\r
3171 appData.premoveBlack) {
\r
3172 sprintf(str, "%s%s\n", ics_prefix,
\r
3173 appData.premoveBlackText);
\r
3174 if (appData.debugMode)
\r
3175 fprintf(debugFP, "Sending premove:\n");
\r
3177 } else if (gotPremove) {
\r
3179 ClearPremoveHighlights();
\r
3180 if (appData.debugMode)
\r
3181 fprintf(debugFP, "Sending premove:\n");
\r
3182 UserMoveEvent(premoveFromX, premoveFromY,
\r
3183 premoveToX, premoveToY,
\r
3184 premovePromoChar);
\r
3188 /* Usually suppress following prompt */
\r
3189 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3190 if (looking_at(buf, &i, "*% ")) {
\r
3191 savingComment = FALSE;
\r
3195 } else if (started == STARTED_HOLDINGS) {
\r
3197 char new_piece[MSG_SIZ];
\r
3198 started = STARTED_NONE;
\r
3199 parse[parse_pos] = NULLCHAR;
\r
3200 if (appData.debugMode)
\r
3201 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3202 parse, currentMove);
\r
3203 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3204 gamenum == ics_gamenum) {
\r
3205 if (gameInfo.variant == VariantNormal) {
\r
3206 /* [HGM] We seem to switch variant during a game!
\r
3207 * Presumably no holdings were displayed, so we have
\r
3208 * to move the position two files to the right to
\r
3209 * create room for them!
\r
3211 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3212 /* Get a move list just to see the header, which
\r
3213 will tell us whether this is really bug or zh */
\r
3214 if (ics_getting_history == H_FALSE) {
\r
3215 ics_getting_history = H_REQUESTED;
\r
3216 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3220 new_piece[0] = NULLCHAR;
\r
3221 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3222 &gamenum, white_holding, black_holding,
\r
3224 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3225 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3226 /* [HGM] copy holdings to board holdings area */
\r
3227 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3228 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3230 if (appData.zippyPlay && first.initDone) {
\r
3231 ZippyHoldings(white_holding, black_holding,
\r
3235 if (tinyLayout || smallLayout) {
\r
3236 char wh[16], bh[16];
\r
3237 PackHolding(wh, white_holding);
\r
3238 PackHolding(bh, black_holding);
\r
3239 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3240 gameInfo.white, gameInfo.black);
\r
3242 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3243 gameInfo.white, white_holding,
\r
3244 gameInfo.black, black_holding);
\r
3247 DrawPosition(FALSE, boards[currentMove]);
\r
3248 DisplayTitle(str);
\r
3250 /* Suppress following prompt */
\r
3251 if (looking_at(buf, &i, "*% ")) {
\r
3252 savingComment = FALSE;
\r
3259 i++; /* skip unparsed character and loop back */
\r
3262 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3263 started != STARTED_HOLDINGS && i > next_out) {
\r
3264 SendToPlayer(&buf[next_out], i - next_out);
\r
3267 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3269 leftover_len = buf_len - leftover_start;
\r
3270 /* if buffer ends with something we couldn't parse,
\r
3271 reparse it after appending the next read */
\r
3273 } else if (count == 0) {
\r
3274 RemoveInputSource(isr);
\r
3275 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3277 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3282 /* Board style 12 looks like this:
\r
3284 <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
\r
3286 * The "<12> " is stripped before it gets to this routine. The two
\r
3287 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3288 * some chess servers may not have them, or may have only the first.
\r
3289 * Additional trailing fields may be added in the future.
\r
3292 #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"
\r
3294 #define RELATION_OBSERVING_PLAYED 0
\r
3295 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3296 #define RELATION_PLAYING_MYMOVE 1
\r
3297 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3298 #define RELATION_EXAMINING 2
\r
3299 #define RELATION_ISOLATED_BOARD -3
\r
3300 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3303 ParseBoard12(string)
\r
3306 GameMode newGameMode;
\r
3307 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3308 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3309 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3310 char to_play, board_chars[200];
\r
3311 char move_str[500], str[500], elapsed_time[500];
\r
3312 char black[32], white[32];
\r
3314 int prevMove = currentMove;
\r
3316 ChessMove moveType;
\r
3317 int fromX, fromY, toX, toY;
\r
3319 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3320 char *bookHit = NULL; // [HGM] book
\r
3322 fromX = fromY = toX = toY = -1;
\r
3326 if (appData.debugMode)
\r
3327 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3329 move_str[0] = NULLCHAR;
\r
3330 elapsed_time[0] = NULLCHAR;
\r
3331 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3333 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3334 if(string[i] == ' ') { ranks++; files = 0; }
\r
3338 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3339 board_chars[i] = '\0';
\r
3342 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3343 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3344 &gamenum, white, black, &relation, &basetime, &increment,
\r
3345 &white_stren, &black_stren, &white_time, &black_time,
\r
3346 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3350 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3351 DisplayError(str, 0);
\r
3355 /* Convert the move number to internal form */
\r
3356 moveNum = (moveNum - 1) * 2;
\r
3357 if (to_play == 'B') moveNum++;
\r
3358 if (moveNum >= MAX_MOVES) {
\r
3359 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3364 switch (relation) {
\r
3365 case RELATION_OBSERVING_PLAYED:
\r
3366 case RELATION_OBSERVING_STATIC:
\r
3367 if (gamenum == -1) {
\r
3368 /* Old ICC buglet */
\r
3369 relation = RELATION_OBSERVING_STATIC;
\r
3371 newGameMode = IcsObserving;
\r
3373 case RELATION_PLAYING_MYMOVE:
\r
3374 case RELATION_PLAYING_NOTMYMOVE:
\r
3376 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3377 IcsPlayingWhite : IcsPlayingBlack;
\r
3379 case RELATION_EXAMINING:
\r
3380 newGameMode = IcsExamining;
\r
3382 case RELATION_ISOLATED_BOARD:
\r
3384 /* Just display this board. If user was doing something else,
\r
3385 we will forget about it until the next board comes. */
\r
3386 newGameMode = IcsIdle;
\r
3388 case RELATION_STARTING_POSITION:
\r
3389 newGameMode = gameMode;
\r
3393 /* Modify behavior for initial board display on move listing
\r
3396 switch (ics_getting_history) {
\r
3400 case H_GOT_REQ_HEADER:
\r
3401 case H_GOT_UNREQ_HEADER:
\r
3402 /* This is the initial position of the current game */
\r
3403 gamenum = ics_gamenum;
\r
3404 moveNum = 0; /* old ICS bug workaround */
\r
3405 if (to_play == 'B') {
\r
3406 startedFromSetupPosition = TRUE;
\r
3407 blackPlaysFirst = TRUE;
\r
3409 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3410 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3411 if (currentMove == 0) currentMove = 1;
\r
3413 newGameMode = gameMode;
\r
3414 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3416 case H_GOT_UNWANTED_HEADER:
\r
3417 /* This is an initial board that we don't want */
\r
3419 case H_GETTING_MOVES:
\r
3420 /* Should not happen */
\r
3421 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3422 ics_getting_history = H_FALSE;
\r
3426 /* Take action if this is the first board of a new game, or of a
\r
3427 different game than is currently being displayed. */
\r
3428 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3429 relation == RELATION_ISOLATED_BOARD) {
\r
3431 /* Forget the old game and get the history (if any) of the new one */
\r
3432 if (gameMode != BeginningOfGame) {
\r
3433 Reset(FALSE, TRUE);
\r
3436 if (appData.autoRaiseBoard) BoardToTop();
\r
3438 if (gamenum == -1) {
\r
3439 newGameMode = IcsIdle;
\r
3440 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3441 appData.getMoveList) {
\r
3442 /* Need to get game history */
\r
3443 ics_getting_history = H_REQUESTED;
\r
3444 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3448 /* Initially flip the board to have black on the bottom if playing
\r
3449 black or if the ICS flip flag is set, but let the user change
\r
3450 it with the Flip View button. */
\r
3451 flipView = appData.autoFlipView ?
\r
3452 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3455 /* Done with values from previous mode; copy in new ones */
\r
3456 gameMode = newGameMode;
\r
3458 ics_gamenum = gamenum;
\r
3459 if (gamenum == gs_gamenum) {
\r
3460 int klen = strlen(gs_kind);
\r
3461 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3462 sprintf(str, "ICS %s", gs_kind);
\r
3463 gameInfo.event = StrSave(str);
\r
3465 gameInfo.event = StrSave("ICS game");
\r
3467 gameInfo.site = StrSave(appData.icsHost);
\r
3468 gameInfo.date = PGNDate();
\r
3469 gameInfo.round = StrSave("-");
\r
3470 gameInfo.white = StrSave(white);
\r
3471 gameInfo.black = StrSave(black);
\r
3472 timeControl = basetime * 60 * 1000;
\r
3473 timeControl_2 = 0;
\r
3474 timeIncrement = increment * 1000;
\r
3475 movesPerSession = 0;
\r
3476 gameInfo.timeControl = TimeControlTagValue();
\r
3477 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3478 if (appData.debugMode) {
\r
3479 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3480 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3481 setbuf(debugFP, NULL);
\r
3484 gameInfo.outOfBook = NULL;
\r
3486 /* Do we have the ratings? */
\r
3487 if (strcmp(player1Name, white) == 0 &&
\r
3488 strcmp(player2Name, black) == 0) {
\r
3489 if (appData.debugMode)
\r
3490 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3491 player1Rating, player2Rating);
\r
3492 gameInfo.whiteRating = player1Rating;
\r
3493 gameInfo.blackRating = player2Rating;
\r
3494 } else if (strcmp(player2Name, white) == 0 &&
\r
3495 strcmp(player1Name, black) == 0) {
\r
3496 if (appData.debugMode)
\r
3497 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3498 player2Rating, player1Rating);
\r
3499 gameInfo.whiteRating = player2Rating;
\r
3500 gameInfo.blackRating = player1Rating;
\r
3502 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3504 /* Silence shouts if requested */
\r
3505 if (appData.quietPlay &&
\r
3506 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3507 SendToICS(ics_prefix);
\r
3508 SendToICS("set shout 0\n");
\r
3512 /* Deal with midgame name changes */
\r
3514 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3515 if (gameInfo.white) free(gameInfo.white);
\r
3516 gameInfo.white = StrSave(white);
\r
3518 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3519 if (gameInfo.black) free(gameInfo.black);
\r
3520 gameInfo.black = StrSave(black);
\r
3524 /* Throw away game result if anything actually changes in examine mode */
\r
3525 if (gameMode == IcsExamining && !newGame) {
\r
3526 gameInfo.result = GameUnfinished;
\r
3527 if (gameInfo.resultDetails != NULL) {
\r
3528 free(gameInfo.resultDetails);
\r
3529 gameInfo.resultDetails = NULL;
\r
3533 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3534 in if they are in a different variation than we are. */
\r
3535 if (pauseExamInvalid) return;
\r
3536 if (pausing && gameMode == IcsExamining) {
\r
3537 if (moveNum <= pauseExamForwardMostMove) {
\r
3538 pauseExamInvalid = TRUE;
\r
3539 forwardMostMove = pauseExamForwardMostMove;
\r
3544 if (appData.debugMode) {
\r
3545 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3547 /* Parse the board */
\r
3548 for (k = 0; k < ranks; k++) {
\r
3549 for (j = 0; j < files; j++)
\r
3550 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3551 if(gameInfo.holdingsWidth > 1) {
\r
3552 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3553 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3556 CopyBoard(boards[moveNum], board);
\r
3557 if (moveNum == 0) {
\r
3558 startedFromSetupPosition =
\r
3559 !CompareBoards(board, initialPosition);
\r
3560 if(startedFromSetupPosition)
\r
3561 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3564 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3565 to make it also work for FRC opening positions. Note that board12
\r
3566 is really defective for later FRC positions, as it has no way to
\r
3567 indicate which Rook can castle if they are on the same side of King.
\r
3568 For the initial position we grant rights to the outermost Rooks,
\r
3569 and remember thos rights, and we then copy them on positions
\r
3570 later in an FRC game. This means WB might not recognize castlings with
\r
3571 Rooks that have moved back to their original position as illegal,
\r
3572 but in ICS mode that is not its job anyway.
\r
3574 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3575 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3577 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3578 if(board[0][i] == WhiteRook) j = i;
\r
3579 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3580 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3581 if(board[0][i] == WhiteRook) j = i;
\r
3582 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3583 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3584 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3585 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3586 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3587 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3588 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3590 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3591 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3592 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3593 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3594 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3595 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3597 r = castlingRights[moveNum][0] = initialRights[0];
\r
3598 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3599 r = castlingRights[moveNum][1] = initialRights[1];
\r
3600 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3601 r = castlingRights[moveNum][3] = initialRights[3];
\r
3602 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3603 r = castlingRights[moveNum][4] = initialRights[4];
\r
3604 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3605 /* wildcastle kludge: always assume King has rights */
\r
3606 r = castlingRights[moveNum][2] = initialRights[2];
\r
3607 r = castlingRights[moveNum][5] = initialRights[5];
\r
3609 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3610 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3613 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3614 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3615 /* This was an initial position from a move list, not
\r
3616 the current position */
\r
3620 /* Update currentMove and known move number limits */
\r
3621 newMove = newGame || moveNum > forwardMostMove;
\r
3623 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3624 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3625 takeback = forwardMostMove - moveNum;
\r
3626 for (i = 0; i < takeback; i++) {
\r
3627 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3628 SendToProgram("undo\n", &first);
\r
3633 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3634 if (gameMode == IcsExamining && moveNum == 0) {
\r
3635 /* Workaround for ICS limitation: we are not told the wild
\r
3636 type when starting to examine a game. But if we ask for
\r
3637 the move list, the move list header will tell us */
\r
3638 ics_getting_history = H_REQUESTED;
\r
3639 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3642 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3643 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3644 forwardMostMove = moveNum;
\r
3645 if (!pausing || currentMove > forwardMostMove)
\r
3646 currentMove = forwardMostMove;
\r
3648 /* New part of history that is not contiguous with old part */
\r
3649 if (pausing && gameMode == IcsExamining) {
\r
3650 pauseExamInvalid = TRUE;
\r
3651 forwardMostMove = pauseExamForwardMostMove;
\r
3654 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3655 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3656 ics_getting_history = H_REQUESTED;
\r
3657 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3662 /* Update the clocks */
\r
3663 if (strchr(elapsed_time, '.')) {
\r
3664 /* Time is in ms */
\r
3665 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3666 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3668 /* Time is in seconds */
\r
3669 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3670 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3675 if (appData.zippyPlay && newGame &&
\r
3676 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3677 gameMode != IcsExamining)
\r
3678 ZippyFirstBoard(moveNum, basetime, increment);
\r
3681 /* Put the move on the move list, first converting
\r
3682 to canonical algebraic form. */
\r
3683 if (moveNum > 0) {
\r
3684 if (appData.debugMode) {
\r
3685 if (appData.debugMode) { int f = forwardMostMove;
\r
3686 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3687 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3689 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3690 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3691 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3692 setbuf(debugFP, NULL);
\r
3694 if (moveNum <= backwardMostMove) {
\r
3695 /* We don't know what the board looked like before
\r
3696 this move. Punt. */
\r
3697 strcpy(parseList[moveNum - 1], move_str);
\r
3698 strcat(parseList[moveNum - 1], " ");
\r
3699 strcat(parseList[moveNum - 1], elapsed_time);
\r
3700 moveList[moveNum - 1][0] = NULLCHAR;
\r
3701 } else if (strcmp(move_str, "none") == 0) {
\r
3702 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3703 /* Again, we don't know what the board looked like;
\r
3704 this is really the start of the game. */
\r
3705 parseList[moveNum - 1][0] = NULLCHAR;
\r
3706 moveList[moveNum - 1][0] = NULLCHAR;
\r
3707 backwardMostMove = moveNum;
\r
3708 startedFromSetupPosition = TRUE;
\r
3709 fromX = fromY = toX = toY = -1;
\r
3711 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3712 // So we parse the long-algebraic move string in stead of the SAN move
\r
3713 int valid; char buf[MSG_SIZ], *prom;
\r
3715 // str looks something like "Q/a1-a2"; kill the slash
\r
3716 if(str[1] == '/')
\r
3717 sprintf(buf, "%c%s", str[0], str+2);
\r
3718 else strcpy(buf, str); // might be castling
\r
3719 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3720 strcat(buf, prom); // long move lacks promo specification!
\r
3721 if(!appData.testLegality) {
\r
3722 if(appData.debugMode)
\r
3723 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3724 strcpy(move_str, buf);
\r
3726 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3727 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3728 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3729 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3730 // end of long SAN patch
\r
3732 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3733 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3734 fromY, fromX, toY, toX, promoChar,
\r
3735 parseList[moveNum-1]);
\r
3736 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3737 castlingRights[moveNum]) ) {
\r
3739 case MT_STALEMATE:
\r
3743 if(gameInfo.variant != VariantShogi)
\r
3744 strcat(parseList[moveNum - 1], "+");
\r
3746 case MT_CHECKMATE:
\r
3747 strcat(parseList[moveNum - 1], "#");
\r
3750 strcat(parseList[moveNum - 1], " ");
\r
3751 strcat(parseList[moveNum - 1], elapsed_time);
\r
3752 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3753 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3754 strcat(moveList[moveNum - 1], "\n");
\r
3756 /* Move from ICS was illegal!? Punt. */
\r
3757 if (appData.debugMode) {
\r
3758 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3759 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3762 if (appData.testLegality && appData.debugMode) {
\r
3763 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3764 DisplayError(str, 0);
\r
3767 strcpy(parseList[moveNum - 1], move_str);
\r
3768 strcat(parseList[moveNum - 1], " ");
\r
3769 strcat(parseList[moveNum - 1], elapsed_time);
\r
3770 moveList[moveNum - 1][0] = NULLCHAR;
\r
3771 fromX = fromY = toX = toY = -1;
\r
3774 if (appData.debugMode) {
\r
3775 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3776 setbuf(debugFP, NULL);
\r
3780 /* Send move to chess program (BEFORE animating it). */
\r
3781 if (appData.zippyPlay && !newGame && newMove &&
\r
3782 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3784 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3785 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3786 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3787 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3789 DisplayError(str, 0);
\r
3791 if (first.sendTime) {
\r
3792 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3794 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3795 if (firstMove && !bookHit) {
\r
3796 firstMove = FALSE;
\r
3797 if (first.useColors) {
\r
3798 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3800 "black\ngo\n", &first);
\r
3802 SendToProgram("go\n", &first);
\r
3804 first.maybeThinking = TRUE;
\r
3807 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3808 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3809 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3810 DisplayError(str, 0);
\r
3812 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3813 SendMoveToProgram(moveNum - 1, &first);
\r
3820 if (moveNum > 0 && !gotPremove) {
\r
3821 /* If move comes from a remote source, animate it. If it
\r
3822 isn't remote, it will have already been animated. */
\r
3823 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3824 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3826 if (!pausing && appData.highlightLastMove) {
\r
3827 SetHighlights(fromX, fromY, toX, toY);
\r
3831 /* Start the clocks */
\r
3832 whiteFlag = blackFlag = FALSE;
\r
3833 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3834 if (ticking == 0) {
\r
3835 ics_clock_paused = TRUE;
\r
3837 } else if (ticking == 1) {
\r
3838 ics_clock_paused = FALSE;
\r
3840 if (gameMode == IcsIdle ||
\r
3841 relation == RELATION_OBSERVING_STATIC ||
\r
3842 relation == RELATION_EXAMINING ||
\r
3844 DisplayBothClocks();
\r
3848 /* Display opponents and material strengths */
\r
3849 if (gameInfo.variant != VariantBughouse &&
\r
3850 gameInfo.variant != VariantCrazyhouse) {
\r
3851 if (tinyLayout || smallLayout) {
\r
3852 if(gameInfo.variant == VariantNormal)
\r
3853 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3854 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3855 basetime, increment);
\r
3857 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3858 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3859 basetime, increment, (int) gameInfo.variant);
\r
3861 if(gameInfo.variant == VariantNormal)
\r
3862 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3863 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3864 basetime, increment);
\r
3866 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3867 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3868 basetime, increment, VariantName(gameInfo.variant));
\r
3870 DisplayTitle(str);
\r
3871 if (appData.debugMode) {
\r
3872 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3877 /* Display the board */
\r
3880 if (appData.premove)
\r
3881 if (!gotPremove ||
\r
3882 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3883 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3884 ClearPremoveHighlights();
\r
3886 DrawPosition(FALSE, boards[currentMove]);
\r
3887 DisplayMove(moveNum - 1);
\r
3888 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3892 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3894 if(bookHit) { // [HGM] book: simulate book reply
\r
3895 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3897 programStats.nodes = programStats.depth = programStats.time =
\r
3898 programStats.score = programStats.got_only_move = 0;
\r
3899 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3901 strcpy(bookMove, "move ");
\r
3902 strcat(bookMove, bookHit);
\r
3903 HandleMachineMove(bookMove, &first);
\r
3909 GetMoveListEvent()
\r
3911 char buf[MSG_SIZ];
\r
3912 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3913 ics_getting_history = H_REQUESTED;
\r
3914 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3920 AnalysisPeriodicEvent(force)
\r
3923 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3924 && !force) || !appData.periodicUpdates)
\r
3927 /* Send . command to Crafty to collect stats */
\r
3928 SendToProgram(".\n", &first);
\r
3930 /* Don't send another until we get a response (this makes
\r
3931 us stop sending to old Crafty's which don't understand
\r
3932 the "." command (sending illegal cmds resets node count & time,
\r
3933 which looks bad)) */
\r
3934 programStats.ok_to_send = 0;
\r
3938 SendMoveToProgram(moveNum, cps)
\r
3940 ChessProgramState *cps;
\r
3942 char buf[MSG_SIZ];
\r
3944 if (cps->useUsermove) {
\r
3945 SendToProgram("usermove ", cps);
\r
3947 if (cps->useSAN) {
\r
3949 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3950 int len = space - parseList[moveNum];
\r
3951 memcpy(buf, parseList[moveNum], len);
\r
3952 buf[len++] = '\n';
\r
3953 buf[len] = NULLCHAR;
\r
3955 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3957 SendToProgram(buf, cps);
\r
3959 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3960 AlphaRank(moveList[moveNum], 4);
\r
3961 SendToProgram(moveList[moveNum], cps);
\r
3962 AlphaRank(moveList[moveNum], 4); // and back
\r
3964 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3965 * the engine. It would be nice to have a better way to identify castle
\r
3967 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3968 && cps->useOOCastle) {
\r
3969 int fromX = moveList[moveNum][0] - AAA;
\r
3970 int fromY = moveList[moveNum][1] - ONE;
\r
3971 int toX = moveList[moveNum][2] - AAA;
\r
3972 int toY = moveList[moveNum][3] - ONE;
\r
3973 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3974 && boards[moveNum][toY][toX] == WhiteRook)
\r
3975 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3976 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3977 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3978 else SendToProgram("O-O-O\n", cps);
\r
3980 else SendToProgram(moveList[moveNum], cps);
\r
3982 else SendToProgram(moveList[moveNum], cps);
\r
3983 /* End of additions by Tord */
\r
3986 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3987 /* Send 'go' if we are in a mode where machine should play. */
\r
3988 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3989 (gameMode == TwoMachinesPlay ||
\r
3991 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3993 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3994 SendToProgram("go\n", cps);
\r
3995 if (appData.debugMode) {
\r
3996 fprintf(debugFP, "(extra)\n");
\r
3999 setboardSpoiledMachineBlack = 0;
\r
4003 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
4004 ChessMove moveType;
\r
4005 int fromX, fromY, toX, toY;
\r
4007 char user_move[MSG_SIZ];
\r
4009 switch (moveType) {
\r
4011 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4012 (int)moveType, fromX, fromY, toX, toY);
\r
4013 DisplayError(user_move + strlen("say "), 0);
\r
4015 case WhiteKingSideCastle:
\r
4016 case BlackKingSideCastle:
\r
4017 case WhiteQueenSideCastleWild:
\r
4018 case BlackQueenSideCastleWild:
\r
4020 case WhiteHSideCastleFR:
\r
4021 case BlackHSideCastleFR:
\r
4023 sprintf(user_move, "o-o\n");
\r
4025 case WhiteQueenSideCastle:
\r
4026 case BlackQueenSideCastle:
\r
4027 case WhiteKingSideCastleWild:
\r
4028 case BlackKingSideCastleWild:
\r
4030 case WhiteASideCastleFR:
\r
4031 case BlackASideCastleFR:
\r
4033 sprintf(user_move, "o-o-o\n");
\r
4035 case WhitePromotionQueen:
\r
4036 case BlackPromotionQueen:
\r
4037 case WhitePromotionRook:
\r
4038 case BlackPromotionRook:
\r
4039 case WhitePromotionBishop:
\r
4040 case BlackPromotionBishop:
\r
4041 case WhitePromotionKnight:
\r
4042 case BlackPromotionKnight:
\r
4043 case WhitePromotionKing:
\r
4044 case BlackPromotionKing:
\r
4045 case WhitePromotionChancellor:
\r
4046 case BlackPromotionChancellor:
\r
4047 case WhitePromotionArchbishop:
\r
4048 case BlackPromotionArchbishop:
\r
4049 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4050 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4051 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4052 PieceToChar(WhiteFerz));
\r
4053 else if(gameInfo.variant == VariantGreat)
\r
4054 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4055 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4056 PieceToChar(WhiteMan));
\r
4058 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4059 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4060 PieceToChar(PromoPiece(moveType)));
\r
4064 sprintf(user_move, "%c@%c%c\n",
\r
4065 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4066 AAA + toX, ONE + toY);
\r
4069 case WhiteCapturesEnPassant:
\r
4070 case BlackCapturesEnPassant:
\r
4071 case IllegalMove: /* could be a variant we don't quite understand */
\r
4072 sprintf(user_move, "%c%c%c%c\n",
\r
4073 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4076 SendToICS(user_move);
\r
4080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4081 int rf, ff, rt, ft;
\r
4085 if (rf == DROP_RANK) {
\r
4086 sprintf(move, "%c@%c%c\n",
\r
4087 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4089 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4090 sprintf(move, "%c%c%c%c\n",
\r
4091 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4093 sprintf(move, "%c%c%c%c%c\n",
\r
4094 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4100 ProcessICSInitScript(f)
\r
4103 char buf[MSG_SIZ];
\r
4105 while (fgets(buf, MSG_SIZ, f)) {
\r
4106 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4113 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4115 AlphaRank(char *move, int n)
\r
4117 // char *p = move, c; int x, y;
\r
4119 if (appData.debugMode) {
\r
4120 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4123 if(move[1]=='*' &&
\r
4124 move[2]>='0' && move[2]<='9' &&
\r
4125 move[3]>='a' && move[3]<='x' ) {
\r
4127 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4128 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4130 if(move[0]>='0' && move[0]<='9' &&
\r
4131 move[1]>='a' && move[1]<='x' &&
\r
4132 move[2]>='0' && move[2]<='9' &&
\r
4133 move[3]>='a' && move[3]<='x' ) {
\r
4134 /* input move, Shogi -> normal */
\r
4135 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4136 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4137 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4138 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4140 if(move[1]=='@' &&
\r
4141 move[3]>='0' && move[3]<='9' &&
\r
4142 move[2]>='a' && move[2]<='x' ) {
\r
4144 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4145 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4148 move[0]>='a' && move[0]<='x' &&
\r
4149 move[3]>='0' && move[3]<='9' &&
\r
4150 move[2]>='a' && move[2]<='x' ) {
\r
4151 /* output move, normal -> Shogi */
\r
4152 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4153 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4154 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4155 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4156 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4158 if (appData.debugMode) {
\r
4159 fprintf(debugFP, " out = '%s'\n", move);
\r
4163 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4165 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4168 ChessMove *moveType;
\r
4169 int *fromX, *fromY, *toX, *toY;
\r
4172 if (appData.debugMode) {
\r
4173 fprintf(debugFP, "move to parse: %s\n", move);
\r
4175 *moveType = yylexstr(moveNum, move);
\r
4177 switch (*moveType) {
\r
4178 case WhitePromotionChancellor:
\r
4179 case BlackPromotionChancellor:
\r
4180 case WhitePromotionArchbishop:
\r
4181 case BlackPromotionArchbishop:
\r
4182 case WhitePromotionQueen:
\r
4183 case BlackPromotionQueen:
\r
4184 case WhitePromotionRook:
\r
4185 case BlackPromotionRook:
\r
4186 case WhitePromotionBishop:
\r
4187 case BlackPromotionBishop:
\r
4188 case WhitePromotionKnight:
\r
4189 case BlackPromotionKnight:
\r
4190 case WhitePromotionKing:
\r
4191 case BlackPromotionKing:
\r
4193 case WhiteCapturesEnPassant:
\r
4194 case BlackCapturesEnPassant:
\r
4195 case WhiteKingSideCastle:
\r
4196 case WhiteQueenSideCastle:
\r
4197 case BlackKingSideCastle:
\r
4198 case BlackQueenSideCastle:
\r
4199 case WhiteKingSideCastleWild:
\r
4200 case WhiteQueenSideCastleWild:
\r
4201 case BlackKingSideCastleWild:
\r
4202 case BlackQueenSideCastleWild:
\r
4203 /* Code added by Tord: */
\r
4204 case WhiteHSideCastleFR:
\r
4205 case WhiteASideCastleFR:
\r
4206 case BlackHSideCastleFR:
\r
4207 case BlackASideCastleFR:
\r
4208 /* End of code added by Tord */
\r
4209 case IllegalMove: /* bug or odd chess variant */
\r
4210 *fromX = currentMoveString[0] - AAA;
\r
4211 *fromY = currentMoveString[1] - ONE;
\r
4212 *toX = currentMoveString[2] - AAA;
\r
4213 *toY = currentMoveString[3] - ONE;
\r
4214 *promoChar = currentMoveString[4];
\r
4215 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4216 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4217 if (appData.debugMode) {
\r
4218 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4220 *fromX = *fromY = *toX = *toY = 0;
\r
4223 if (appData.testLegality) {
\r
4224 return (*moveType != IllegalMove);
\r
4226 return !(fromX == fromY && toX == toY);
\r
4231 *fromX = *moveType == WhiteDrop ?
\r
4232 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4233 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4234 *fromY = DROP_RANK;
\r
4235 *toX = currentMoveString[2] - AAA;
\r
4236 *toY = currentMoveString[3] - ONE;
\r
4237 *promoChar = NULLCHAR;
\r
4240 case AmbiguousMove:
\r
4241 case ImpossibleMove:
\r
4242 case (ChessMove) 0: /* end of file */
\r
4251 if (appData.debugMode) {
\r
4252 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4255 *fromX = *fromY = *toX = *toY = 0;
\r
4256 *promoChar = NULLCHAR;
\r
4261 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
\r
4262 // All positions will have equal probability, but the current method will not provide a unique
\r
4263 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4268 int squaresLeft[4];
\r
4269 int piecesLeft[(int)BlackPawn];
\r
4270 int seed, nrOfShuffles;
\r
4272 void GetPositionNumber()
\r
4273 { // sets global variable seed
\r
4276 seed = appData.defaultFrcPosition;
\r
4277 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4278 for(i=0; i<50; i++) seed += random();
\r
4279 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4280 if(seed<0) seed = -seed;
\r
4284 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4285 // put the piece on the (n-1)-th empty squares of the given shade
\r
4289 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4290 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4291 board[rank][i] = (ChessSquare) pieceType;
\r
4292 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
\r
4293 squaresLeft[ANY]--;
\r
4294 piecesLeft[pieceType]--;
\r
4302 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4303 // calculate where the next piece goes, (any empty square), and put it there
\r
4307 i = seed % squaresLeft[shade];
\r
4308 nrOfShuffles *= squaresLeft[shade];
\r
4309 seed /= squaresLeft[shade];
\r
4310 put(board, pieceType, rank, i, shade);
\r
4313 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4314 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4316 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4318 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4319 i = seed % k; // pick one
\r
4320 nrOfShuffles *= k;
\r
4322 while(i >= j) i -= j--;
\r
4323 j = n - 1 - j; i += j;
\r
4324 put(board, pieceType, rank, j, ANY);
\r
4325 put(board, pieceType, rank, i, ANY);
\r
4328 void SetUpShuffle(Board board, int number)
\r
4330 int i, p, first=1;
\r
4332 GetPositionNumber(); nrOfShuffles = 1;
\r
4334 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4335 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4336 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4338 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4340 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4341 p = (int) board[0][i];
\r
4342 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4343 board[0][i] = EmptySquare;
\r
4346 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4347 // shuffles restricted to allow normal castling put KRR first
\r
4348 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4349 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4350 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4351 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4352 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4353 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4354 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4355 put(board, WhiteRook, 0, 0, ANY);
\r
4356 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4359 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
\r
4360 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4361 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4362 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4363 while(piecesLeft[p] >= 2) {
\r
4364 AddOnePiece(board, p, 0, LITE);
\r
4365 AddOnePiece(board, p, 0, DARK);
\r
4367 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4370 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4371 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4372 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4373 if(p == (int)WhiteRook) continue;
\r
4374 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4375 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4378 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4380 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4381 // Last King gets castling rights
\r
4382 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4383 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4384 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4387 while(piecesLeft[(int)WhiteKing]) {
\r
4388 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4389 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4394 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4395 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4398 // Only Rooks can be left; simply place them all
\r
4399 while(piecesLeft[(int)WhiteRook]) {
\r
4400 i = put(board, WhiteRook, 0, 0, ANY);
\r
4401 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4404 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4406 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4409 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4410 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4413 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4416 int SetCharTable( char *table, const char * map )
\r
4417 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4418 /* Basically a safe strcpy that uses the last character as King */
\r
4420 int result = FALSE; int NrPieces;
\r
4422 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4423 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4424 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4426 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4427 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4428 table[i] = map[i];
\r
4429 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4431 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4432 table[(int) BlackKing] = map[NrPieces-1];
\r
4440 void Prelude(Board board)
\r
4441 { // [HGM] superchess: random selection of exo-pieces
\r
4442 int i, j, k; ChessSquare p;
\r
4443 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4445 GetPositionNumber(); // use FRC position number
\r
4447 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4448 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4449 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4450 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4453 j = seed%4; seed /= 4;
\r
4454 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4455 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4456 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4457 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4458 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4459 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4460 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4461 j = seed%3; seed /= 3;
\r
4462 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4463 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4464 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4465 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4466 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4467 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4468 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4469 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4470 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4471 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4472 put(board, exoPieces[0], 0, 0, ANY);
\r
4473 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4477 InitPosition(redraw)
\r
4480 ChessSquare (* pieces)[BOARD_SIZE];
\r
4481 int i, j, pawnRow, overrule,
\r
4482 oldx = gameInfo.boardWidth,
\r
4483 oldy = gameInfo.boardHeight,
\r
4484 oldh = gameInfo.holdingsWidth,
\r
4485 oldv = gameInfo.variant;
\r
4487 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4488 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4490 /* [AS] Initialize pv info list [HGM] and game status */
\r
4492 for( i=0; i<MAX_MOVES; i++ ) {
\r
4493 pvInfoList[i].depth = 0;
\r
4494 epStatus[i]=EP_NONE;
\r
4495 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4498 initialRulePlies = 0; /* 50-move counter start */
\r
4500 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4501 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4505 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4506 /* the initialized data only consist of the two backranks. The switch */
\r
4507 /* selects which one we will use, which is than copied to the Board */
\r
4508 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4509 /* empty squares. This initial position is then copied to boards[0], */
\r
4510 /* possibly after shuffling, so that it remains available. */
\r
4512 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4513 gameInfo.boardWidth = 8;
\r
4514 gameInfo.boardHeight = 8;
\r
4515 gameInfo.holdingsSize = 0;
\r
4516 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4517 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4518 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4520 switch (gameInfo.variant) {
\r
4521 case VariantFischeRandom:
\r
4522 shuffleOpenings = TRUE;
\r
4524 pieces = FIDEArray;
\r
4526 case VariantShatranj:
\r
4527 pieces = ShatranjArray;
\r
4528 nrCastlingRights = 0;
\r
4529 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4531 case VariantTwoKings:
\r
4532 pieces = twoKingsArray;
\r
4534 case VariantCapaRandom:
\r
4535 shuffleOpenings = TRUE;
\r
4536 case VariantCapablanca:
\r
4537 pieces = CapablancaArray;
\r
4538 gameInfo.boardWidth = 10;
\r
4539 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4541 case VariantGothic:
\r
4542 pieces = GothicArray;
\r
4543 gameInfo.boardWidth = 10;
\r
4544 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4546 case VariantJanus:
\r
4547 pieces = JanusArray;
\r
4548 gameInfo.boardWidth = 10;
\r
4549 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4550 nrCastlingRights = 6;
\r
4551 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4552 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4553 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
\r
4554 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4555 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4556 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
\r
4558 case VariantFalcon:
\r
4559 pieces = FalconArray;
\r
4560 gameInfo.boardWidth = 10;
\r
4561 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4563 case VariantXiangqi:
\r
4564 pieces = XiangqiArray;
\r
4565 gameInfo.boardWidth = 9;
\r
4566 gameInfo.boardHeight = 10;
\r
4567 nrCastlingRights = 0;
\r
4568 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4570 case VariantShogi:
\r
4571 pieces = ShogiArray;
\r
4572 gameInfo.boardWidth = 9;
\r
4573 gameInfo.boardHeight = 9;
\r
4574 gameInfo.holdingsSize = 7;
\r
4575 nrCastlingRights = 0;
\r
4576 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4578 case VariantCourier:
\r
4579 pieces = CourierArray;
\r
4580 gameInfo.boardWidth = 12;
\r
4581 nrCastlingRights = 0;
\r
4582 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4583 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4585 case VariantKnightmate:
\r
4586 pieces = KnightmateArray;
\r
4587 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4589 case VariantFairy:
\r
4590 pieces = fairyArray;
\r
4591 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4593 case VariantGreat:
\r
4594 pieces = GreatArray;
\r
4595 gameInfo.boardWidth = 10;
\r
4596 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4597 gameInfo.holdingsSize = 8;
\r
4599 case VariantSuper:
\r
4600 pieces = FIDEArray;
\r
4601 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4602 gameInfo.holdingsSize = 8;
\r
4603 startedFromSetupPosition = TRUE;
\r
4605 case VariantCrazyhouse:
\r
4606 case VariantBughouse:
\r
4607 pieces = FIDEArray;
\r
4608 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4609 gameInfo.holdingsSize = 5;
\r
4611 case VariantWildCastle:
\r
4612 pieces = FIDEArray;
\r
4613 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4614 shuffleOpenings = 1;
\r
4616 case VariantNoCastle:
\r
4617 pieces = FIDEArray;
\r
4618 nrCastlingRights = 0;
\r
4619 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4620 /* !!?unconstrained back-rank shuffle */
\r
4621 shuffleOpenings = 1;
\r
4626 if(appData.NrFiles >= 0) {
\r
4627 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4628 gameInfo.boardWidth = appData.NrFiles;
\r
4630 if(appData.NrRanks >= 0) {
\r
4631 gameInfo.boardHeight = appData.NrRanks;
\r
4633 if(appData.holdingsSize >= 0) {
\r
4634 i = appData.holdingsSize;
\r
4635 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4636 gameInfo.holdingsSize = i;
\r
4638 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4639 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4640 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4642 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4643 if(pawnRow < 1) pawnRow = 1;
\r
4645 /* User pieceToChar list overrules defaults */
\r
4646 if(appData.pieceToCharTable != NULL)
\r
4647 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4649 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4651 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4652 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4653 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4654 initialPosition[i][j] = s;
\r
4656 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4657 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4658 initialPosition[pawnRow][j] = WhitePawn;
\r
4659 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4660 if(gameInfo.variant == VariantXiangqi) {
\r
4662 initialPosition[pawnRow][j] =
\r
4663 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4664 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4665 initialPosition[2][j] = WhiteCannon;
\r
4666 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4670 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4672 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4675 initialPosition[1][j] = WhiteBishop;
\r
4676 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4678 initialPosition[1][j] = WhiteRook;
\r
4679 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4682 if( nrCastlingRights == -1) {
\r
4683 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4684 /* This sets default castling rights from none to normal corners */
\r
4685 /* Variants with other castling rights must set them themselves above */
\r
4686 nrCastlingRights = 6;
\r
4688 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4689 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4690 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4691 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4692 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4693 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4696 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4697 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4698 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
\r
4699 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
\r
4700 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4701 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4704 if(gameInfo.variant == VariantFischeRandom) {
\r
4705 if( appData.defaultFrcPosition < 0 ) {
\r
4706 ShuffleFRC( initialPosition );
\r
4709 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4711 startedFromSetupPosition = TRUE;
\r
4714 if (appData.debugMode) {
\r
4715 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4717 if(shuffleOpenings) {
\r
4718 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4719 startedFromSetupPosition = TRUE;
\r
4722 if(startedFromPositionFile) {
\r
4723 /* [HGM] loadPos: use PositionFile for every new game */
\r
4724 CopyBoard(initialPosition, filePosition);
\r
4725 for(i=0; i<nrCastlingRights; i++)
\r
4726 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4727 startedFromSetupPosition = TRUE;
\r
4730 CopyBoard(boards[0], initialPosition);
\r
4732 if(oldx != gameInfo.boardWidth ||
\r
4733 oldy != gameInfo.boardHeight ||
\r
4734 oldh != gameInfo.holdingsWidth
\r
4736 || oldv == VariantGothic || // For licensing popups
\r
4737 gameInfo.variant == VariantGothic
\r
4740 || oldv == VariantFalcon ||
\r
4741 gameInfo.variant == VariantFalcon
\r
4744 InitDrawingSizes(-2 ,0);
\r
4747 DrawPosition(TRUE, boards[currentMove]);
\r
4751 SendBoard(cps, moveNum)
\r
4752 ChessProgramState *cps;
\r
4755 char message[MSG_SIZ];
\r
4757 if (cps->useSetboard) {
\r
4758 char* fen = PositionToFEN(moveNum, cps->fenOverride);
\r
4759 sprintf(message, "setboard %s\n", fen);
\r
4760 SendToProgram(message, cps);
\r
4766 /* Kludge to set black to move, avoiding the troublesome and now
\r
4767 * deprecated "black" command.
\r
4769 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4771 SendToProgram("edit\n", cps);
\r
4772 SendToProgram("#\n", cps);
\r
4773 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4774 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4775 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4776 if ((int) *bp < (int) BlackPawn) {
\r
4777 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4778 AAA + j, ONE + i);
\r
4779 if(message[0] == '+' || message[0] == '~') {
\r
4780 sprintf(message, "%c%c%c+\n",
\r
4781 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4782 AAA + j, ONE + i);
\r
4784 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4785 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4786 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4788 SendToProgram(message, cps);
\r
4793 SendToProgram("c\n", cps);
\r
4794 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4795 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4796 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4797 if (((int) *bp != (int) EmptySquare)
\r
4798 && ((int) *bp >= (int) BlackPawn)) {
\r
4799 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4800 AAA + j, ONE + i);
\r
4801 if(message[0] == '+' || message[0] == '~') {
\r
4802 sprintf(message, "%c%c%c+\n",
\r
4803 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4804 AAA + j, ONE + i);
\r
4806 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4807 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4808 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4810 SendToProgram(message, cps);
\r
4815 SendToProgram(".\n", cps);
\r
4817 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4821 IsPromotion(fromX, fromY, toX, toY)
\r
4822 int fromX, fromY, toX, toY;
\r
4824 /* [HGM] add Shogi promotions */
\r
4825 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4826 ChessSquare piece;
\r
4828 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4829 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4830 /* [HGM] Note to self: line above also weeds out drops */
\r
4831 piece = boards[currentMove][fromY][fromX];
\r
4832 if(gameInfo.variant == VariantShogi) {
\r
4833 promotionZoneSize = 3;
\r
4834 highestPromotingPiece = (int)WhiteKing;
\r
4835 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4836 and if in normal chess we then allow promotion to King, why not
\r
4837 allow promotion of other piece in Shogi? */
\r
4839 if((int)piece >= BlackPawn) {
\r
4840 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4842 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4844 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4845 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4847 return ( (int)piece <= highestPromotingPiece );
\r
4851 InPalace(row, column)
\r
4853 { /* [HGM] for Xiangqi */
\r
4854 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4855 column < (BOARD_WIDTH + 4)/2 &&
\r
4856 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4861 PieceForSquare (x, y)
\r
4865 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4868 return boards[currentMove][y][x];
\r
4872 OKToStartUserMove(x, y)
\r
4875 ChessSquare from_piece;
\r
4878 if (matchMode) return FALSE;
\r
4879 if (gameMode == EditPosition) return TRUE;
\r
4881 if (x >= 0 && y >= 0)
\r
4882 from_piece = boards[currentMove][y][x];
\r
4884 from_piece = EmptySquare;
\r
4886 if (from_piece == EmptySquare) return FALSE;
\r
4888 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4889 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4891 switch (gameMode) {
\r
4892 case PlayFromGameFile:
\r
4894 case TwoMachinesPlay:
\r
4898 case IcsObserving:
\r
4902 case MachinePlaysWhite:
\r
4903 case IcsPlayingBlack:
\r
4904 if (appData.zippyPlay) return FALSE;
\r
4905 if (white_piece) {
\r
4906 DisplayMoveError(_("You are playing Black"));
\r
4911 case MachinePlaysBlack:
\r
4912 case IcsPlayingWhite:
\r
4913 if (appData.zippyPlay) return FALSE;
\r
4914 if (!white_piece) {
\r
4915 DisplayMoveError(_("You are playing White"));
\r
4921 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4922 DisplayMoveError(_("It is White's turn"));
\r
4925 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4926 DisplayMoveError(_("It is Black's turn"));
\r
4929 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4930 /* Editing correspondence game history */
\r
4931 /* Could disallow this or prompt for confirmation */
\r
4932 cmailOldMove = -1;
\r
4934 if (currentMove < forwardMostMove) {
\r
4935 /* Discarding moves */
\r
4936 /* Could prompt for confirmation here,
\r
4937 but I don't think that's such a good idea */
\r
4938 forwardMostMove = currentMove;
\r
4942 case BeginningOfGame:
\r
4943 if (appData.icsActive) return FALSE;
\r
4944 if (!appData.noChessProgram) {
\r
4945 if (!white_piece) {
\r
4946 DisplayMoveError(_("You are playing White"));
\r
4953 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4954 DisplayMoveError(_("It is White's turn"));
\r
4957 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4958 DisplayMoveError(_("It is Black's turn"));
\r
4964 case IcsExamining:
\r
4967 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4968 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4969 DisplayMoveError(_("Displayed position is not current"));
\r
4975 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4976 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4977 int lastLoadGameUseList = FALSE;
\r
4978 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4979 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4983 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4984 int fromX, fromY, toX, toY;
\r
4987 ChessMove moveType;
\r
4988 ChessSquare pdown, pup;
\r
4990 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4991 if ((fromX == toX) && (fromY == toY)) {
\r
4992 return ImpossibleMove;
\r
4995 /* [HGM] suppress all moves into holdings area and guard band */
\r
4996 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4997 return ImpossibleMove;
\r
4999 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5000 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5001 pdown = boards[currentMove][fromY][fromX];
\r
5002 pup = boards[currentMove][toY][toX];
\r
5003 if ( gameMode != EditPosition &&
\r
5004 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5005 WhitePawn <= pup && pup < BlackPawn ||
\r
5006 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5007 BlackPawn <= pup && pup < EmptySquare
\r
5008 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5009 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5010 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5012 return ImpossibleMove;
\r
5014 /* Check if the user is playing in turn. This is complicated because we
\r
5015 let the user "pick up" a piece before it is his turn. So the piece he
\r
5016 tried to pick up may have been captured by the time he puts it down!
\r
5017 Therefore we use the color the user is supposed to be playing in this
\r
5018 test, not the color of the piece that is currently on the starting
\r
5019 square---except in EditGame mode, where the user is playing both
\r
5020 sides; fortunately there the capture race can't happen. (It can
\r
5021 now happen in IcsExamining mode, but that's just too bad. The user
\r
5022 will get a somewhat confusing message in that case.)
\r
5025 switch (gameMode) {
\r
5026 case PlayFromGameFile:
\r
5028 case TwoMachinesPlay:
\r
5030 case IcsObserving:
\r
5032 /* We switched into a game mode where moves are not accepted,
\r
5033 perhaps while the mouse button was down. */
\r
5034 return ImpossibleMove;
\r
5036 case MachinePlaysWhite:
\r
5037 /* User is moving for Black */
\r
5038 if (WhiteOnMove(currentMove)) {
\r
5039 DisplayMoveError(_("It is White's turn"));
\r
5040 return ImpossibleMove;
\r
5044 case MachinePlaysBlack:
\r
5045 /* User is moving for White */
\r
5046 if (!WhiteOnMove(currentMove)) {
\r
5047 DisplayMoveError(_("It is Black's turn"));
\r
5048 return ImpossibleMove;
\r
5053 case IcsExamining:
\r
5054 case BeginningOfGame:
\r
5057 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5058 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5059 /* User is moving for Black */
\r
5060 if (WhiteOnMove(currentMove)) {
\r
5061 DisplayMoveError(_("It is White's turn"));
\r
5062 return ImpossibleMove;
\r
5065 /* User is moving for White */
\r
5066 if (!WhiteOnMove(currentMove)) {
\r
5067 DisplayMoveError(_("It is Black's turn"));
\r
5068 return ImpossibleMove;
\r
5073 case IcsPlayingBlack:
\r
5074 /* User is moving for Black */
\r
5075 if (WhiteOnMove(currentMove)) {
\r
5076 if (!appData.premove) {
\r
5077 DisplayMoveError(_("It is White's turn"));
\r
5078 } else if (toX >= 0 && toY >= 0) {
\r
5081 premoveFromX = fromX;
\r
5082 premoveFromY = fromY;
\r
5083 premovePromoChar = promoChar;
\r
5085 if (appData.debugMode)
\r
5086 fprintf(debugFP, "Got premove: fromX %d,"
\r
5087 "fromY %d, toX %d, toY %d\n",
\r
5088 fromX, fromY, toX, toY);
\r
5090 return ImpossibleMove;
\r
5094 case IcsPlayingWhite:
\r
5095 /* User is moving for White */
\r
5096 if (!WhiteOnMove(currentMove)) {
\r
5097 if (!appData.premove) {
\r
5098 DisplayMoveError(_("It is Black's turn"));
\r
5099 } else if (toX >= 0 && toY >= 0) {
\r
5102 premoveFromX = fromX;
\r
5103 premoveFromY = fromY;
\r
5104 premovePromoChar = promoChar;
\r
5106 if (appData.debugMode)
\r
5107 fprintf(debugFP, "Got premove: fromX %d,"
\r
5108 "fromY %d, toX %d, toY %d\n",
\r
5109 fromX, fromY, toX, toY);
\r
5111 return ImpossibleMove;
\r
5118 case EditPosition:
\r
5119 /* EditPosition, empty square, or different color piece;
\r
5120 click-click move is possible */
\r
5121 if (toX == -2 || toY == -2) {
\r
5122 boards[0][fromY][fromX] = EmptySquare;
\r
5123 return AmbiguousMove;
\r
5124 } else if (toX >= 0 && toY >= 0) {
\r
5125 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5126 boards[0][fromY][fromX] = EmptySquare;
\r
5127 return AmbiguousMove;
\r
5129 return ImpossibleMove;
\r
5132 /* [HGM] If move started in holdings, it means a drop */
\r
5133 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5134 if( pup != EmptySquare ) return ImpossibleMove;
\r
5135 if(appData.testLegality) {
\r
5136 /* it would be more logical if LegalityTest() also figured out
\r
5137 * which drops are legal. For now we forbid pawns on back rank.
\r
5138 * Shogi is on its own here...
\r
5140 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5141 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5142 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5144 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5147 userOfferedDraw = FALSE;
\r
5149 /* [HGM] always test for legality, to get promotion info */
\r
5150 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5151 epStatus[currentMove], castlingRights[currentMove],
\r
5152 fromY, fromX, toY, toX, promoChar);
\r
5154 /* [HGM] but possibly ignore an IllegalMove result */
\r
5155 if (appData.testLegality) {
\r
5156 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5157 DisplayMoveError(_("Illegal move"));
\r
5158 return ImpossibleMove;
\r
5161 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5163 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5164 function is made into one that returns an OK move type if FinishMove
\r
5165 should be called. This to give the calling driver routine the
\r
5166 opportunity to finish the userMove input with a promotion popup,
\r
5167 without bothering the user with this for invalid or illegal moves */
\r
5169 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5172 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5174 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5175 ChessMove moveType;
\r
5176 int fromX, fromY, toX, toY;
\r
5177 /*char*/int promoChar;
\r
5179 char *bookHit = 0;
\r
5180 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5181 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5182 // [HGM] superchess: suppress promotions to non-available piece
\r
5183 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5184 if(WhiteOnMove(currentMove)) {
\r
5185 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5187 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5191 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5192 move type in caller when we know the move is a legal promotion */
\r
5193 if(moveType == NormalMove && promoChar)
\r
5194 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5195 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5196 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5197 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5198 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5199 fromX = boards[currentMove][fromY][fromX];
\r
5200 fromY = DROP_RANK;
\r
5203 /* [HGM] <popupFix> The following if has been moved here from
\r
5204 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5205 piece drops in training games?), and because it can only be
\r
5206 performed after it is known to what we promote. */
\r
5207 if (gameMode == Training) {
\r
5208 /* compare the move played on the board to the next move in the
\r
5209 * game. If they match, display the move and the opponent's response.
\r
5210 * If they don't match, display an error message.
\r
5214 CopyBoard(testBoard, boards[currentMove]);
\r
5215 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5217 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5218 ForwardInner(currentMove+1);
\r
5220 /* Autoplay the opponent's response.
\r
5221 * if appData.animate was TRUE when Training mode was entered,
\r
5222 * the response will be animated.
\r
5224 saveAnimate = appData.animate;
\r
5225 appData.animate = animateTraining;
\r
5226 ForwardInner(currentMove+1);
\r
5227 appData.animate = saveAnimate;
\r
5229 /* check for the end of the game */
\r
5230 if (currentMove >= forwardMostMove) {
\r
5231 gameMode = PlayFromGameFile;
\r
5233 SetTrainingModeOff();
\r
5234 DisplayInformation(_("End of game"));
\r
5237 DisplayError(_("Incorrect move"), 0);
\r
5242 /* Ok, now we know that the move is good, so we can kill
\r
5243 the previous line in Analysis Mode */
\r
5244 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5245 forwardMostMove = currentMove;
\r
5248 /* If we need the chess program but it's dead, restart it */
\r
5249 ResurrectChessProgram();
\r
5251 /* A user move restarts a paused game*/
\r
5255 thinkOutput[0] = NULLCHAR;
\r
5257 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5259 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5260 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5261 // [HGM] superchess: take promotion piece out of holdings
\r
5262 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5263 if(WhiteOnMove(forwardMostMove-1)) {
\r
5264 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5265 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5267 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5268 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5272 if (gameMode == BeginningOfGame) {
\r
5273 if (appData.noChessProgram) {
\r
5274 gameMode = EditGame;
\r
5277 char buf[MSG_SIZ];
\r
5278 gameMode = MachinePlaysBlack;
\r
5281 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5282 DisplayTitle(buf);
\r
5283 if (first.sendName) {
\r
5284 sprintf(buf, "name %s\n", gameInfo.white);
\r
5285 SendToProgram(buf, &first);
\r
5291 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5292 /* Relay move to ICS or chess engine */
\r
5293 if (appData.icsActive) {
\r
5294 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5295 gameMode == IcsExamining) {
\r
5296 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5297 ics_user_moved = 1;
\r
5300 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5301 gameMode == MachinePlaysWhite ||
\r
5302 gameMode == MachinePlaysBlack)) {
\r
5303 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5305 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5306 // [HGM] book: if program might be playing, let it use book
\r
5307 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5308 first.maybeThinking = TRUE;
\r
5309 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5310 if (currentMove == cmailOldMove + 1) {
\r
5311 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5315 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5317 switch (gameMode) {
\r
5319 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5320 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5324 case MT_CHECKMATE:
\r
5325 if (WhiteOnMove(currentMove)) {
\r
5326 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5328 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5331 case MT_STALEMATE:
\r
5332 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5337 case MachinePlaysBlack:
\r
5338 case MachinePlaysWhite:
\r
5339 /* disable certain menu options while machine is thinking */
\r
5340 SetMachineThinkingEnables();
\r
5347 if(bookHit) { // [HGM] book: simulate book reply
\r
5348 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5350 programStats.nodes = programStats.depth = programStats.time =
\r
5351 programStats.score = programStats.got_only_move = 0;
\r
5352 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5354 strcpy(bookMove, "move ");
\r
5355 strcat(bookMove, bookHit);
\r
5356 HandleMachineMove(bookMove, &first);
\r
5362 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5363 int fromX, fromY, toX, toY;
\r
5366 /* [HGM] This routine was added to allow calling of its two logical
\r
5367 parts from other modules in the old way. Before, UserMoveEvent()
\r
5368 automatically called FinishMove() if the move was OK, and returned
\r
5369 otherwise. I separated the two, in order to make it possible to
\r
5370 slip a promotion popup in between. But that it always needs two
\r
5371 calls, to the first part, (now called UserMoveTest() ), and to
\r
5372 FinishMove if the first part succeeded. Calls that do not need
\r
5373 to do anything in between, can call this routine the old way.
\r
5375 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5376 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5377 if(moveType != ImpossibleMove)
\r
5378 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5381 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5383 // char * hint = lastHint;
\r
5384 FrontEndProgramStats stats;
\r
5386 stats.which = cps == &first ? 0 : 1;
\r
5387 stats.depth = cpstats->depth;
\r
5388 stats.nodes = cpstats->nodes;
\r
5389 stats.score = cpstats->score;
\r
5390 stats.time = cpstats->time;
\r
5391 stats.pv = cpstats->movelist;
\r
5392 stats.hint = lastHint;
\r
5393 stats.an_move_index = 0;
\r
5394 stats.an_move_count = 0;
\r
5396 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5397 stats.hint = cpstats->move_name;
\r
5398 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5399 stats.an_move_count = cpstats->nr_moves;
\r
5402 SetProgramStats( &stats );
\r
5405 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5406 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5407 char *bookHit = NULL;
\r
5409 //first determine if the incoming move brings opponent into his book
\r
5410 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5411 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5412 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5413 if(bookHit != NULL && !cps->bookSuspend) {
\r
5414 // make sure opponent is not going to reply after receiving move to book position
\r
5415 SendToProgram("force\n", cps);
\r
5416 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5418 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5419 // now arrange restart after book miss
\r
5421 // after a book hit we never send 'go', and the code after the call to this routine
\r
5422 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5423 char buf[MSG_SIZ];
\r
5424 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5425 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5426 SendToProgram(buf, cps);
\r
5427 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5428 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5429 SendToProgram("go\n", cps);
\r
5430 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5431 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5432 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5433 SendToProgram("go\n", cps);
\r
5434 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5436 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5439 char *savedMessage;
\r
5440 ChessProgramState *savedState;
\r
5441 void DeferredBookMove(void)
\r
5443 if(savedState->lastPing != savedState->lastPong)
\r
5444 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5446 HandleMachineMove(savedMessage, savedState);
\r
5450 HandleMachineMove(message, cps)
\r
5452 ChessProgramState *cps;
\r
5454 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5455 char realname[MSG_SIZ];
\r
5456 int fromX, fromY, toX, toY;
\r
5457 ChessMove moveType;
\r
5463 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5465 * Kludge to ignore BEL characters
\r
5467 while (*message == '\007') message++;
\r
5470 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5472 if(cps->debug && *message == '#') return;
\r
5475 * Look for book output
\r
5477 if (cps == &first && bookRequested) {
\r
5478 if (message[0] == '\t' || message[0] == ' ') {
\r
5479 /* Part of the book output is here; append it */
\r
5480 strcat(bookOutput, message);
\r
5481 strcat(bookOutput, " \n");
\r
5483 } else if (bookOutput[0] != NULLCHAR) {
\r
5484 /* All of book output has arrived; display it */
\r
5485 char *p = bookOutput;
\r
5486 while (*p != NULLCHAR) {
\r
5487 if (*p == '\t') *p = ' ';
\r
5490 DisplayInformation(bookOutput);
\r
5491 bookRequested = FALSE;
\r
5492 /* Fall through to parse the current output */
\r
5497 * Look for machine move.
\r
5499 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5500 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5502 /* This method is only useful on engines that support ping */
\r
5503 if (cps->lastPing != cps->lastPong) {
\r
5504 if (gameMode == BeginningOfGame) {
\r
5505 /* Extra move from before last new; ignore */
\r
5506 if (appData.debugMode) {
\r
5507 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5510 if (appData.debugMode) {
\r
5511 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5512 cps->which, gameMode);
\r
5515 SendToProgram("undo\n", cps);
\r
5520 switch (gameMode) {
\r
5521 case BeginningOfGame:
\r
5522 /* Extra move from before last reset; ignore */
\r
5523 if (appData.debugMode) {
\r
5524 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5531 /* Extra move after we tried to stop. The mode test is
\r
5532 not a reliable way of detecting this problem, but it's
\r
5533 the best we can do on engines that don't support ping.
\r
5535 if (appData.debugMode) {
\r
5536 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5537 cps->which, gameMode);
\r
5539 SendToProgram("undo\n", cps);
\r
5542 case MachinePlaysWhite:
\r
5543 case IcsPlayingWhite:
\r
5544 machineWhite = TRUE;
\r
5547 case MachinePlaysBlack:
\r
5548 case IcsPlayingBlack:
\r
5549 machineWhite = FALSE;
\r
5552 case TwoMachinesPlay:
\r
5553 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5556 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5557 if (appData.debugMode) {
\r
5559 "Ignoring move out of turn by %s, gameMode %d"
\r
5560 ", forwardMost %d\n",
\r
5561 cps->which, gameMode, forwardMostMove);
\r
5566 if (appData.debugMode) { int f = forwardMostMove;
\r
5567 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5568 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5570 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5571 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5572 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5573 /* Machine move could not be parsed; ignore it. */
\r
5574 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5575 machineMove, cps->which);
\r
5576 DisplayError(buf1, 0);
\r
5577 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5578 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5579 if (gameMode == TwoMachinesPlay) {
\r
5580 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5586 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5587 /* So we have to redo legality test with true e.p. status here, */
\r
5588 /* to make sure an illegal e.p. capture does not slip through, */
\r
5589 /* to cause a forfeit on a justified illegal-move complaint */
\r
5590 /* of the opponent. */
\r
5591 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5592 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5594 ChessMove moveType;
\r
5595 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5596 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5597 fromY, fromX, toY, toX, promoChar);
\r
5598 if (appData.debugMode) {
\r
5600 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5601 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5602 fprintf(debugFP, "castling rights\n");
\r
5604 if(moveType == IllegalMove) {
\r
5605 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5606 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5607 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5610 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5611 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5612 when they shouldn't (like TSCP-Gothic) */
\r
5613 switch(moveType) {
\r
5614 case WhiteASideCastleFR:
\r
5615 case BlackASideCastleFR:
\r
5617 currentMoveString[2]++;
\r
5619 case WhiteHSideCastleFR:
\r
5620 case BlackHSideCastleFR:
\r
5622 currentMoveString[2]--;
\r
5624 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5627 hintRequested = FALSE;
\r
5628 lastHint[0] = NULLCHAR;
\r
5629 bookRequested = FALSE;
\r
5630 /* Program may be pondering now */
\r
5631 cps->maybeThinking = TRUE;
\r
5632 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5633 if (cps->offeredDraw) cps->offeredDraw--;
\r
5636 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5638 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5639 ics_user_moved = 1;
\r
5640 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5641 char buf[3*MSG_SIZ];
\r
5643 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5644 programStats.depth,
\r
5645 programStats.score / 100.,
\r
5646 programStats.time / 100.,
\r
5647 u64ToDouble(programStats.nodes),
\r
5648 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5649 programStats.movelist);
\r
5654 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5655 strcpy(machineMove, currentMoveString);
\r
5656 strcat(machineMove, "\n");
\r
5657 strcpy(moveList[forwardMostMove], machineMove);
\r
5659 /* [AS] Save move info and clear stats for next move */
\r
5660 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5661 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5662 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5663 ClearProgramStats();
\r
5664 thinkOutput[0] = NULLCHAR;
\r
5665 hiddenThinkOutputState = 0;
\r
5667 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5669 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5670 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5673 while( count < adjudicateLossPlies ) {
\r
5674 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5677 score = -score; /* Flip score for winning side */
\r
5680 if( score > adjudicateLossThreshold ) {
\r
5687 if( count >= adjudicateLossPlies ) {
\r
5688 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5690 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5691 "Xboard adjudication",
\r
5698 if( gameMode == TwoMachinesPlay ) {
\r
5699 // [HGM] some adjudications useful with buggy engines
\r
5700 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5701 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5703 if( appData.testLegality )
\r
5704 { /* [HGM] Some more adjudications for obstinate engines */
\r
5705 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5706 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
\r
5707 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5708 static int moveCount = 6;
\r
5710 /* Count what is on board. */
\r
5711 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5712 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5716 { /* count B,N,R and other of each side */
\r
5719 NrK++; break; // [HGM] atomic: count Kings
\r
5723 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5724 bishopsColor |= 1 << ((i^j)&1);
\r
5729 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5730 bishopsColor |= 1 << ((i^j)&1);
\r
5740 case EmptySquare:
\r
5745 PawnAdvance += m; NrPawns++;
\r
5747 NrPieces += (p != EmptySquare);
\r
5748 NrW += ((int)p < (int)BlackPawn);
\r
5749 if(gameInfo.variant == VariantXiangqi &&
\r
5750 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5751 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5752 NrW -= ((int)p < (int)BlackPawn);
\r
5756 /* Some material-based adjudications that have to be made before stalemate test */
\r
5757 if(gameInfo.variant == VariantAtomic && NrK < 2) {
\r
5758 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
\r
5759 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
\r
5760 if(appData.checkMates) {
\r
5761 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5762 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5763 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5764 "Xboard adjudication: King destroyed", GE_XBOARD );
\r
5769 /* Bare King in Shatranj (loses) or Losers (wins) */
\r
5770 if( NrW == 1 || NrPieces - NrW == 1) {
\r
5771 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
\r
5772 epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win
\r
5773 if(appData.checkMates) {
\r
5774 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5775 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5776 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5777 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5781 if( gameInfo.variant == VariantShatranj && --bare < 0)
\r
5783 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm
\r
5784 if(appData.checkMates) {
\r
5785 /* but only adjudicate if adjudication enabled */
\r
5786 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5787 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5788 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5789 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5796 // don't wait for engine to announce game end if we can judge ourselves
\r
5797 switch (MateTest(boards[forwardMostMove],
\r
5798 PosFlags(forwardMostMove), epFile,
\r
5799 castlingRights[forwardMostMove]) ) {
\r
5804 case MT_STALEMATE:
\r
5805 if(epStatus[forwardMostMove] != EP_CHECKMATE) // [HGM] spare win through baring or K-capt
\r
5806 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5807 if(appData.checkMates) {
\r
5808 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5809 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5810 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide
\r
5811 || gameInfo.variant == VariantGiveaway) // [HGM] losers:
\r
5812 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!
\r
5813 "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5815 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5819 case MT_CHECKMATE:
\r
5820 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5821 if(appData.checkMates) {
\r
5822 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5823 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5824 GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:
\r
5825 ? BlackWins : WhiteWins, // reverse the result ( A!=1 is !A for a boolean)
\r
5826 "Xboard adjudication: Checkmate", GE_XBOARD );
\r
5832 /* Next absolutely insufficient mating material. */
\r
5833 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5834 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5835 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5836 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5837 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5839 /* always flag draws, for judging claims */
\r
5840 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5842 if(appData.materialDraws) {
\r
5843 /* but only adjudicate them if adjudication enabled */
\r
5844 SendToProgram("force\n", cps->other); // suppress reply
\r
5845 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5846 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5847 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5852 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5853 if(NrPieces == 4 &&
\r
5854 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5855 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5856 || NrWN==2 || NrBN==2 /* KNNK */
\r
5857 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5859 if(--moveCount < 0 && appData.trivialDraws)
\r
5860 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5861 SendToProgram("force\n", cps->other); // suppress reply
\r
5862 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5863 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5864 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5867 } else moveCount = 6;
\r
5871 if (appData.debugMode) { int i;
\r
5872 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5873 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5874 appData.drawRepeats);
\r
5875 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5876 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5880 /* Check for rep-draws */
\r
5882 for(k = forwardMostMove-2;
\r
5883 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5884 epStatus[k] < EP_UNKNOWN &&
\r
5885 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5889 if (appData.debugMode) {
\r
5890 fprintf(debugFP, " loop\n");
\r
5893 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5895 if (appData.debugMode) {
\r
5896 fprintf(debugFP, "match\n");
\r
5899 /* compare castling rights */
\r
5900 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5901 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5902 rights++; /* King lost rights, while rook still had them */
\r
5903 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5904 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5905 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5906 rights++; /* but at least one rook lost them */
\r
5908 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5909 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5911 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5912 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5913 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5917 if (appData.debugMode) {
\r
5918 for(i=0; i<nrCastlingRights; i++)
\r
5919 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5922 if (appData.debugMode) {
\r
5923 fprintf(debugFP, " %d %d\n", rights, k);
\r
5926 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5927 && appData.drawRepeats > 1) {
\r
5928 /* adjudicate after user-specified nr of repeats */
\r
5929 SendToProgram("force\n", cps->other); // suppress reply
\r
5930 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5931 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5932 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5933 // [HGM] xiangqi: check for forbidden perpetuals
\r
5934 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5935 for(m=forwardMostMove; m>k; m-=2) {
\r
5936 if(MateTest(boards[m], PosFlags(m),
\r
5937 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5938 ourPerpetual = 0; // the current mover did not always check
\r
5939 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5940 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5941 hisPerpetual = 0; // the opponent did not always check
\r
5943 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
5944 ourPerpetual, hisPerpetual);
\r
5945 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5946 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5947 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
5950 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
5951 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
5952 // Now check for perpetual chases
\r
5953 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
5954 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
5955 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
5956 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5957 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5958 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
5961 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
5962 break; // Abort repetition-checking loop.
\r
5964 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
5966 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5969 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5970 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5974 /* Now we test for 50-move draws. Determine ply count */
\r
5975 count = forwardMostMove;
\r
5976 /* look for last irreversble move */
\r
5977 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5979 /* if we hit starting position, add initial plies */
\r
5980 if( count == backwardMostMove )
\r
5981 count -= initialRulePlies;
\r
5982 count = forwardMostMove - count;
\r
5984 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5985 /* this is used to judge if draw claims are legal */
\r
5986 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5987 SendToProgram("force\n", cps->other); // suppress reply
\r
5988 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5989 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5990 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5994 /* if draw offer is pending, treat it as a draw claim
\r
5995 * when draw condition present, to allow engines a way to
\r
5996 * claim draws before making their move to avoid a race
\r
5997 * condition occurring after their move
\r
5999 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6001 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6002 p = "Draw claim: 50-move rule";
\r
6003 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6004 p = "Draw claim: 3-fold repetition";
\r
6005 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6006 p = "Draw claim: insufficient mating material";
\r
6008 SendToProgram("force\n", cps->other); // suppress reply
\r
6009 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6010 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6011 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6017 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6018 SendToProgram("force\n", cps->other); // suppress reply
\r
6019 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6020 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6022 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6029 if (gameMode == TwoMachinesPlay) {
\r
6030 /* [HGM] relaying draw offers moved to after reception of move */
\r
6031 /* and interpreting offer as claim if it brings draw condition */
\r
6032 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6033 SendToProgram("draw\n", cps->other);
\r
6035 if (cps->other->sendTime) {
\r
6036 SendTimeRemaining(cps->other,
\r
6037 cps->other->twoMachinesColor[0] == 'w');
\r
6039 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6040 if (firstMove && !bookHit) {
\r
6041 firstMove = FALSE;
\r
6042 if (cps->other->useColors) {
\r
6043 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6045 SendToProgram("go\n", cps->other);
\r
6047 cps->other->maybeThinking = TRUE;
\r
6050 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6052 if (!pausing && appData.ringBellAfterMoves) {
\r
6057 * Reenable menu items that were disabled while
\r
6058 * machine was thinking
\r
6060 if (gameMode != TwoMachinesPlay)
\r
6061 SetUserThinkingEnables();
\r
6063 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6064 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6065 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6067 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6069 strcpy(bookMove, "move ");
\r
6070 strcat(bookMove, bookHit);
\r
6071 message = bookMove;
\r
6073 programStats.nodes = programStats.depth = programStats.time =
\r
6074 programStats.score = programStats.got_only_move = 0;
\r
6075 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6077 if(cps->lastPing != cps->lastPong) {
\r
6078 savedMessage = message; // args for deferred call
\r
6080 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6083 goto FakeBookMove;
\r
6089 /* Set special modes for chess engines. Later something general
\r
6090 * could be added here; for now there is just one kludge feature,
\r
6091 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6092 * when "xboard" is given as an interactive command.
\r
6094 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6095 cps->useSigint = FALSE;
\r
6096 cps->useSigterm = FALSE;
\r
6099 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6100 * want this, I was asked to put it in, and obliged.
\r
6102 if (!strncmp(message, "setboard ", 9)) {
\r
6103 Board initial_position; int i;
\r
6105 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6107 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6108 DisplayError(_("Bad FEN received from engine"), 0);
\r
6111 Reset(FALSE, FALSE);
\r
6112 CopyBoard(boards[0], initial_position);
\r
6113 initialRulePlies = FENrulePlies;
\r
6114 epStatus[0] = FENepStatus;
\r
6115 for( i=0; i<nrCastlingRights; i++ )
\r
6116 castlingRights[0][i] = FENcastlingRights[i];
\r
6117 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6118 else gameMode = MachinePlaysBlack;
\r
6119 DrawPosition(FALSE, boards[currentMove]);
\r
6125 * Look for communication commands
\r
6127 if (!strncmp(message, "telluser ", 9)) {
\r
6128 DisplayNote(message + 9);
\r
6131 if (!strncmp(message, "tellusererror ", 14)) {
\r
6132 DisplayError(message + 14, 0);
\r
6135 if (!strncmp(message, "tellopponent ", 13)) {
\r
6136 if (appData.icsActive) {
\r
6138 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6142 DisplayNote(message + 13);
\r
6146 if (!strncmp(message, "tellothers ", 11)) {
\r
6147 if (appData.icsActive) {
\r
6149 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6155 if (!strncmp(message, "tellall ", 8)) {
\r
6156 if (appData.icsActive) {
\r
6158 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6162 DisplayNote(message + 8);
\r
6166 if (strncmp(message, "warning", 7) == 0) {
\r
6167 /* Undocumented feature, use tellusererror in new code */
\r
6168 DisplayError(message, 0);
\r
6171 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6172 strcpy(realname, cps->tidy);
\r
6173 strcat(realname, " query");
\r
6174 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6177 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6178 * sent until we are logged on. Crafty kibitzes have been known to
\r
6179 * interfere with the login process.
\r
6182 if (!strncmp(message, "tellics ", 8)) {
\r
6183 SendToICS(message + 8);
\r
6187 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6188 SendToICS(ics_prefix);
\r
6189 SendToICS(message + 15);
\r
6193 /* The following are for backward compatibility only */
\r
6194 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6195 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6196 SendToICS(ics_prefix);
\r
6197 SendToICS(message);
\r
6202 if (strncmp(message, "feature ", 8) == 0) {
\r
6203 ParseFeatures(message+8, cps);
\r
6205 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6209 * If the move is illegal, cancel it and redraw the board.
\r
6210 * Also deal with other error cases. Matching is rather loose
\r
6211 * here to accommodate engines written before the spec.
\r
6213 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6214 strncmp(message, "Error", 5) == 0) {
\r
6215 if (StrStr(message, "name") ||
\r
6216 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6217 StrStr(message, "result") || StrStr(message, "board") ||
\r
6218 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6219 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6220 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6221 StrStr(message, "accepted")) {
\r
6224 if (StrStr(message, "protover")) {
\r
6225 /* Program is responding to input, so it's apparently done
\r
6226 initializing, and this error message indicates it is
\r
6227 protocol version 1. So we don't need to wait any longer
\r
6228 for it to initialize and send feature commands. */
\r
6229 FeatureDone(cps, 1);
\r
6230 cps->protocolVersion = 1;
\r
6233 cps->maybeThinking = FALSE;
\r
6235 if (StrStr(message, "draw")) {
\r
6236 /* Program doesn't have "draw" command */
\r
6237 cps->sendDrawOffers = 0;
\r
6240 if (cps->sendTime != 1 &&
\r
6241 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6242 /* Program apparently doesn't have "time" or "otim" command */
\r
6243 cps->sendTime = 0;
\r
6246 if (StrStr(message, "analyze")) {
\r
6247 cps->analysisSupport = FALSE;
\r
6248 cps->analyzing = FALSE;
\r
6249 Reset(FALSE, TRUE);
\r
6250 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6251 DisplayError(buf2, 0);
\r
6254 if (StrStr(message, "(no matching move)st")) {
\r
6255 /* Special kludge for GNU Chess 4 only */
\r
6256 cps->stKludge = TRUE;
\r
6257 SendTimeControl(cps, movesPerSession, timeControl,
\r
6258 timeIncrement, appData.searchDepth,
\r
6262 if (StrStr(message, "(no matching move)sd")) {
\r
6263 /* Special kludge for GNU Chess 4 only */
\r
6264 cps->sdKludge = TRUE;
\r
6265 SendTimeControl(cps, movesPerSession, timeControl,
\r
6266 timeIncrement, appData.searchDepth,
\r
6270 if (!StrStr(message, "llegal")) {
\r
6273 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6274 gameMode == IcsIdle) return;
\r
6275 if (forwardMostMove <= backwardMostMove) return;
\r
6277 /* Following removed: it caused a bug where a real illegal move
\r
6278 message in analyze mored would be ignored. */
\r
6279 if (cps == &first && programStats.ok_to_send == 0) {
\r
6280 /* Bogus message from Crafty responding to "." This filtering
\r
6281 can miss some of the bad messages, but fortunately the bug
\r
6282 is fixed in current Crafty versions, so it doesn't matter. */
\r
6286 if (pausing) PauseEvent();
\r
6287 if (gameMode == PlayFromGameFile) {
\r
6288 /* Stop reading this game file */
\r
6289 gameMode = EditGame;
\r
6292 currentMove = --forwardMostMove;
\r
6293 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6295 DisplayBothClocks();
\r
6296 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6297 parseList[currentMove], cps->which);
\r
6298 DisplayMoveError(buf1);
\r
6299 DrawPosition(FALSE, boards[currentMove]);
\r
6301 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6302 /* only passes fully legal moves */
\r
6303 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6304 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6305 "False illegal-move claim", GE_XBOARD );
\r
6309 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6310 /* Program has a broken "time" command that
\r
6311 outputs a string not ending in newline.
\r
6313 cps->sendTime = 0;
\r
6317 * If chess program startup fails, exit with an error message.
\r
6318 * Attempts to recover here are futile.
\r
6320 if ((StrStr(message, "unknown host") != NULL)
\r
6321 || (StrStr(message, "No remote directory") != NULL)
\r
6322 || (StrStr(message, "not found") != NULL)
\r
6323 || (StrStr(message, "No such file") != NULL)
\r
6324 || (StrStr(message, "can't alloc") != NULL)
\r
6325 || (StrStr(message, "Permission denied") != NULL)) {
\r
6327 cps->maybeThinking = FALSE;
\r
6328 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6329 cps->which, cps->program, cps->host, message);
\r
6330 RemoveInputSource(cps->isr);
\r
6331 DisplayFatalError(buf1, 0, 1);
\r
6336 * Look for hint output
\r
6338 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6339 if (cps == &first && hintRequested) {
\r
6340 hintRequested = FALSE;
\r
6341 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6342 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6343 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6344 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6345 fromY, fromX, toY, toX, promoChar, buf1);
\r
6346 sprintf(buf2, _("Hint: %s"), buf1);
\r
6347 DisplayInformation(buf2);
\r
6349 /* Hint move could not be parsed!? */
\r
6351 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6352 buf1, cps->which);
\r
6353 DisplayError(buf2, 0);
\r
6356 strcpy(lastHint, buf1);
\r
6362 * Ignore other messages if game is not in progress
\r
6364 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6365 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6368 * look for win, lose, draw, or draw offer
\r
6370 if (strncmp(message, "1-0", 3) == 0) {
\r
6371 char *p, *q, *r = "";
\r
6372 p = strchr(message, '{');
\r
6374 q = strchr(p, '}');
\r
6380 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6382 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6383 char *p, *q, *r = "";
\r
6384 p = strchr(message, '{');
\r
6386 q = strchr(p, '}');
\r
6392 /* Kludge for Arasan 4.1 bug */
\r
6393 if (strcmp(r, "Black resigns") == 0) {
\r
6394 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6397 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6399 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6400 char *p, *q, *r = "";
\r
6401 p = strchr(message, '{');
\r
6403 q = strchr(p, '}');
\r
6410 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6413 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6414 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6416 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6417 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6419 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6420 strncmp(message, "Black matches", 13) == 0 ) {
\r
6421 /* [HGM] ignore GNUShogi noises */
\r
6423 } else if (strncmp(message, "White", 5) == 0 &&
\r
6424 message[5] != '(' &&
\r
6425 StrStr(message, "Black") == NULL) {
\r
6426 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6428 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6429 message[5] != '(') {
\r
6430 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6432 } else if (strcmp(message, "resign") == 0 ||
\r
6433 strcmp(message, "computer resigns") == 0) {
\r
6434 switch (gameMode) {
\r
6435 case MachinePlaysBlack:
\r
6436 case IcsPlayingBlack:
\r
6437 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6439 case MachinePlaysWhite:
\r
6440 case IcsPlayingWhite:
\r
6441 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6443 case TwoMachinesPlay:
\r
6444 if (cps->twoMachinesColor[0] == 'w')
\r
6445 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6447 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6450 /* can't happen */
\r
6454 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6455 switch (gameMode) {
\r
6456 case MachinePlaysBlack:
\r
6457 case IcsPlayingBlack:
\r
6458 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6460 case MachinePlaysWhite:
\r
6461 case IcsPlayingWhite:
\r
6462 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6464 case TwoMachinesPlay:
\r
6465 if (cps->twoMachinesColor[0] == 'w')
\r
6466 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6468 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6471 /* can't happen */
\r
6475 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6476 switch (gameMode) {
\r
6477 case MachinePlaysBlack:
\r
6478 case IcsPlayingBlack:
\r
6479 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6481 case MachinePlaysWhite:
\r
6482 case IcsPlayingWhite:
\r
6483 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6485 case TwoMachinesPlay:
\r
6486 if (cps->twoMachinesColor[0] == 'w')
\r
6487 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6489 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6492 /* can't happen */
\r
6496 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6497 if (WhiteOnMove(forwardMostMove)) {
\r
6498 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6500 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6503 } else if (strstr(message, "Draw") != NULL ||
\r
6504 strstr(message, "game is a draw") != NULL) {
\r
6505 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6507 } else if (strstr(message, "offer") != NULL &&
\r
6508 strstr(message, "draw") != NULL) {
\r
6510 if (appData.zippyPlay && first.initDone) {
\r
6511 /* Relay offer to ICS */
\r
6512 SendToICS(ics_prefix);
\r
6513 SendToICS("draw\n");
\r
6516 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6517 if (gameMode == TwoMachinesPlay) {
\r
6518 if (cps->other->offeredDraw) {
\r
6519 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6520 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6521 /* until after we also have move, to see if it is really claim */
\r
6525 if (cps->other->sendDrawOffers) {
\r
6526 SendToProgram("draw\n", cps->other);
\r
6530 } else if (gameMode == MachinePlaysWhite ||
\r
6531 gameMode == MachinePlaysBlack) {
\r
6532 if (userOfferedDraw) {
\r
6533 DisplayInformation(_("Machine accepts your draw offer"));
\r
6534 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6536 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6543 * Look for thinking output
\r
6545 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6546 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6548 int plylev, mvleft, mvtot, curscore, time;
\r
6549 char mvname[MOVE_LEN];
\r
6550 u64 nodes; // [DM]
\r
6552 int ignore = FALSE;
\r
6553 int prefixHint = FALSE;
\r
6554 mvname[0] = NULLCHAR;
\r
6556 switch (gameMode) {
\r
6557 case MachinePlaysBlack:
\r
6558 case IcsPlayingBlack:
\r
6559 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6561 case MachinePlaysWhite:
\r
6562 case IcsPlayingWhite:
\r
6563 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6568 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6569 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6571 case TwoMachinesPlay:
\r
6572 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6582 buf1[0] = NULLCHAR;
\r
6583 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6584 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6586 if (plyext != ' ' && plyext != '\t') {
\r
6590 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6591 if( cps->scoreIsAbsolute &&
\r
6592 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6594 curscore = -curscore;
\r
6598 programStats.depth = plylev;
\r
6599 programStats.nodes = nodes;
\r
6600 programStats.time = time;
\r
6601 programStats.score = curscore;
\r
6602 programStats.got_only_move = 0;
\r
6604 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6607 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6608 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6609 if(WhiteOnMove(forwardMostMove))
\r
6610 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6611 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6614 /* Buffer overflow protection */
\r
6615 if (buf1[0] != NULLCHAR) {
\r
6616 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6617 && appData.debugMode) {
\r
6619 "PV is too long; using the first %d bytes.\n",
\r
6620 sizeof(programStats.movelist) - 1);
\r
6623 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6625 sprintf(programStats.movelist, " no PV\n");
\r
6628 if (programStats.seen_stat) {
\r
6629 programStats.ok_to_send = 1;
\r
6632 if (strchr(programStats.movelist, '(') != NULL) {
\r
6633 programStats.line_is_book = 1;
\r
6634 programStats.nr_moves = 0;
\r
6635 programStats.moves_left = 0;
\r
6637 programStats.line_is_book = 0;
\r
6640 SendProgramStatsToFrontend( cps, &programStats );
\r
6643 [AS] Protect the thinkOutput buffer from overflow... this
\r
6644 is only useful if buf1 hasn't overflowed first!
\r
6646 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6648 (gameMode == TwoMachinesPlay ?
\r
6649 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6650 ((double) curscore) / 100.0,
\r
6651 prefixHint ? lastHint : "",
\r
6652 prefixHint ? " " : "" );
\r
6654 if( buf1[0] != NULLCHAR ) {
\r
6655 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6657 if( strlen(buf1) > max_len ) {
\r
6658 if( appData.debugMode) {
\r
6659 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6661 buf1[max_len+1] = '\0';
\r
6664 strcat( thinkOutput, buf1 );
\r
6667 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6668 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6669 DisplayMove(currentMove - 1);
\r
6670 DisplayAnalysis();
\r
6674 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6675 /* crafty (9.25+) says "(only move) <move>"
\r
6676 * if there is only 1 legal move
\r
6678 sscanf(p, "(only move) %s", buf1);
\r
6679 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6680 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6681 programStats.depth = 1;
\r
6682 programStats.nr_moves = 1;
\r
6683 programStats.moves_left = 1;
\r
6684 programStats.nodes = 1;
\r
6685 programStats.time = 1;
\r
6686 programStats.got_only_move = 1;
\r
6688 /* Not really, but we also use this member to
\r
6689 mean "line isn't going to change" (Crafty
\r
6690 isn't searching, so stats won't change) */
\r
6691 programStats.line_is_book = 1;
\r
6693 SendProgramStatsToFrontend( cps, &programStats );
\r
6695 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6696 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6697 DisplayMove(currentMove - 1);
\r
6698 DisplayAnalysis();
\r
6701 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6702 &time, &nodes, &plylev, &mvleft,
\r
6703 &mvtot, mvname) >= 5) {
\r
6704 /* The stat01: line is from Crafty (9.29+) in response
\r
6705 to the "." command */
\r
6706 programStats.seen_stat = 1;
\r
6707 cps->maybeThinking = TRUE;
\r
6709 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6712 programStats.depth = plylev;
\r
6713 programStats.time = time;
\r
6714 programStats.nodes = nodes;
\r
6715 programStats.moves_left = mvleft;
\r
6716 programStats.nr_moves = mvtot;
\r
6717 strcpy(programStats.move_name, mvname);
\r
6718 programStats.ok_to_send = 1;
\r
6719 programStats.movelist[0] = '\0';
\r
6721 SendProgramStatsToFrontend( cps, &programStats );
\r
6723 DisplayAnalysis();
\r
6726 } else if (strncmp(message,"++",2) == 0) {
\r
6727 /* Crafty 9.29+ outputs this */
\r
6728 programStats.got_fail = 2;
\r
6731 } else if (strncmp(message,"--",2) == 0) {
\r
6732 /* Crafty 9.29+ outputs this */
\r
6733 programStats.got_fail = 1;
\r
6736 } else if (thinkOutput[0] != NULLCHAR &&
\r
6737 strncmp(message, " ", 4) == 0) {
\r
6738 unsigned message_len;
\r
6741 while (*p && *p == ' ') p++;
\r
6743 message_len = strlen( p );
\r
6745 /* [AS] Avoid buffer overflow */
\r
6746 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6747 strcat(thinkOutput, " ");
\r
6748 strcat(thinkOutput, p);
\r
6751 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6752 strcat(programStats.movelist, " ");
\r
6753 strcat(programStats.movelist, p);
\r
6756 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6757 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6758 DisplayMove(currentMove - 1);
\r
6759 DisplayAnalysis();
\r
6765 buf1[0] = NULLCHAR;
\r
6767 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6768 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6770 ChessProgramStats cpstats;
\r
6772 if (plyext != ' ' && plyext != '\t') {
\r
6776 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6777 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6778 curscore = -curscore;
\r
6781 cpstats.depth = plylev;
\r
6782 cpstats.nodes = nodes;
\r
6783 cpstats.time = time;
\r
6784 cpstats.score = curscore;
\r
6785 cpstats.got_only_move = 0;
\r
6786 cpstats.movelist[0] = '\0';
\r
6788 if (buf1[0] != NULLCHAR) {
\r
6789 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6792 cpstats.ok_to_send = 0;
\r
6793 cpstats.line_is_book = 0;
\r
6794 cpstats.nr_moves = 0;
\r
6795 cpstats.moves_left = 0;
\r
6797 SendProgramStatsToFrontend( cps, &cpstats );
\r
6804 /* Parse a game score from the character string "game", and
\r
6805 record it as the history of the current game. The game
\r
6806 score is NOT assumed to start from the standard position.
\r
6807 The display is not updated in any way.
\r
6810 ParseGameHistory(game)
\r
6813 ChessMove moveType;
\r
6814 int fromX, fromY, toX, toY, boardIndex;
\r
6817 char buf[MSG_SIZ];
\r
6819 if (appData.debugMode)
\r
6820 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6822 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6823 gameInfo.site = StrSave(appData.icsHost);
\r
6824 gameInfo.date = PGNDate();
\r
6825 gameInfo.round = StrSave("-");
\r
6827 /* Parse out names of players */
\r
6828 while (*game == ' ') game++;
\r
6830 while (*game != ' ') *p++ = *game++;
\r
6832 gameInfo.white = StrSave(buf);
\r
6833 while (*game == ' ') game++;
\r
6835 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6837 gameInfo.black = StrSave(buf);
\r
6840 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6843 yyboardindex = boardIndex;
\r
6844 moveType = (ChessMove) yylex();
\r
6845 switch (moveType) {
\r
6846 case IllegalMove: /* maybe suicide chess, etc. */
\r
6847 if (appData.debugMode) {
\r
6848 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6849 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6850 setbuf(debugFP, NULL);
\r
6852 case WhitePromotionChancellor:
\r
6853 case BlackPromotionChancellor:
\r
6854 case WhitePromotionArchbishop:
\r
6855 case BlackPromotionArchbishop:
\r
6856 case WhitePromotionQueen:
\r
6857 case BlackPromotionQueen:
\r
6858 case WhitePromotionRook:
\r
6859 case BlackPromotionRook:
\r
6860 case WhitePromotionBishop:
\r
6861 case BlackPromotionBishop:
\r
6862 case WhitePromotionKnight:
\r
6863 case BlackPromotionKnight:
\r
6864 case WhitePromotionKing:
\r
6865 case BlackPromotionKing:
\r
6867 case WhiteCapturesEnPassant:
\r
6868 case BlackCapturesEnPassant:
\r
6869 case WhiteKingSideCastle:
\r
6870 case WhiteQueenSideCastle:
\r
6871 case BlackKingSideCastle:
\r
6872 case BlackQueenSideCastle:
\r
6873 case WhiteKingSideCastleWild:
\r
6874 case WhiteQueenSideCastleWild:
\r
6875 case BlackKingSideCastleWild:
\r
6876 case BlackQueenSideCastleWild:
\r
6878 case WhiteHSideCastleFR:
\r
6879 case WhiteASideCastleFR:
\r
6880 case BlackHSideCastleFR:
\r
6881 case BlackASideCastleFR:
\r
6883 fromX = currentMoveString[0] - AAA;
\r
6884 fromY = currentMoveString[1] - ONE;
\r
6885 toX = currentMoveString[2] - AAA;
\r
6886 toY = currentMoveString[3] - ONE;
\r
6887 promoChar = currentMoveString[4];
\r
6891 fromX = moveType == WhiteDrop ?
\r
6892 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6893 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6894 fromY = DROP_RANK;
\r
6895 toX = currentMoveString[2] - AAA;
\r
6896 toY = currentMoveString[3] - ONE;
\r
6897 promoChar = NULLCHAR;
\r
6899 case AmbiguousMove:
\r
6901 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6902 if (appData.debugMode) {
\r
6903 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6904 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6905 setbuf(debugFP, NULL);
\r
6907 DisplayError(buf, 0);
\r
6909 case ImpossibleMove:
\r
6911 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6912 if (appData.debugMode) {
\r
6913 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6914 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6915 setbuf(debugFP, NULL);
\r
6917 DisplayError(buf, 0);
\r
6919 case (ChessMove) 0: /* end of file */
\r
6920 if (boardIndex < backwardMostMove) {
\r
6921 /* Oops, gap. How did that happen? */
\r
6922 DisplayError(_("Gap in move list"), 0);
\r
6925 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6926 if (boardIndex > forwardMostMove) {
\r
6927 forwardMostMove = boardIndex;
\r
6931 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6932 strcat(parseList[boardIndex-1], " ");
\r
6933 strcat(parseList[boardIndex-1], yy_text);
\r
6945 case GameUnfinished:
\r
6946 if (gameMode == IcsExamining) {
\r
6947 if (boardIndex < backwardMostMove) {
\r
6948 /* Oops, gap. How did that happen? */
\r
6951 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6954 gameInfo.result = moveType;
\r
6955 p = strchr(yy_text, '{');
\r
6956 if (p == NULL) p = strchr(yy_text, '(');
\r
6959 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6961 q = strchr(p, *p == '{' ? '}' : ')');
\r
6962 if (q != NULL) *q = NULLCHAR;
\r
6965 gameInfo.resultDetails = StrSave(p);
\r
6968 if (boardIndex >= forwardMostMove &&
\r
6969 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6970 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6973 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6974 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6975 parseList[boardIndex]);
\r
6976 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6977 /* currentMoveString is set as a side-effect of yylex */
\r
6978 strcpy(moveList[boardIndex], currentMoveString);
\r
6979 strcat(moveList[boardIndex], "\n");
\r
6981 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6982 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6983 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6985 case MT_STALEMATE:
\r
6989 if(gameInfo.variant != VariantShogi)
\r
6990 strcat(parseList[boardIndex - 1], "+");
\r
6992 case MT_CHECKMATE:
\r
6993 strcat(parseList[boardIndex - 1], "#");
\r
7000 /* Apply a move to the given board */
\r
7002 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7003 int fromX, fromY, toX, toY;
\r
7007 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7009 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7010 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7011 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7014 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7015 oldEP = epStatus[p-1];
\r
7016 epStatus[p] = EP_NONE;
\r
7018 if( board[toY][toX] != EmptySquare )
\r
7019 epStatus[p] = EP_CAPTURE;
\r
7021 if( board[fromY][fromX] == WhitePawn ) {
\r
7022 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7023 epStatus[p] = EP_PAWN_MOVE;
\r
7024 if( toY-fromY==2) {
\r
7025 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7026 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7027 epStatus[p] = toX | berolina;
\r
7028 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7029 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7030 epStatus[p] = toX;
\r
7033 if( board[fromY][fromX] == BlackPawn ) {
\r
7034 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7035 epStatus[p] = EP_PAWN_MOVE;
\r
7036 if( toY-fromY== -2) {
\r
7037 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7038 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7039 epStatus[p] = toX | berolina;
\r
7040 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7041 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7042 epStatus[p] = toX;
\r
7046 for(i=0; i<nrCastlingRights; i++) {
\r
7047 castlingRights[p][i] = castlingRights[p-1][i];
\r
7048 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7049 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7050 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7055 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7056 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7057 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7059 if (fromX == toX && fromY == toY) return;
\r
7061 if (fromY == DROP_RANK) {
\r
7062 /* must be first */
\r
7063 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7065 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7066 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7067 if(gameInfo.variant == VariantKnightmate)
\r
7068 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7070 /* Code added by Tord: */
\r
7071 /* FRC castling assumed when king captures friendly rook. */
\r
7072 if (board[fromY][fromX] == WhiteKing &&
\r
7073 board[toY][toX] == WhiteRook) {
\r
7074 board[fromY][fromX] = EmptySquare;
\r
7075 board[toY][toX] = EmptySquare;
\r
7077 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7079 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7081 } else if (board[fromY][fromX] == BlackKing &&
\r
7082 board[toY][toX] == BlackRook) {
\r
7083 board[fromY][fromX] = EmptySquare;
\r
7084 board[toY][toX] = EmptySquare;
\r
7086 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7088 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7090 /* End of code added by Tord */
\r
7092 } else if (board[fromY][fromX] == king
\r
7093 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7094 && toY == fromY && toX > fromX+1) {
\r
7095 board[fromY][fromX] = EmptySquare;
\r
7096 board[toY][toX] = king;
\r
7097 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7098 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7099 } else if (board[fromY][fromX] == king
\r
7100 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7101 && toY == fromY && toX < fromX-1) {
\r
7102 board[fromY][fromX] = EmptySquare;
\r
7103 board[toY][toX] = king;
\r
7104 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7105 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7106 } else if (board[fromY][fromX] == WhitePawn
\r
7107 && toY == BOARD_HEIGHT-1
\r
7108 && gameInfo.variant != VariantXiangqi
\r
7110 /* white pawn promotion */
\r
7111 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7112 if (board[toY][toX] == EmptySquare) {
\r
7113 board[toY][toX] = WhiteQueen;
\r
7115 if(gameInfo.variant==VariantBughouse ||
\r
7116 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7117 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7118 board[fromY][fromX] = EmptySquare;
\r
7119 } else if ((fromY == BOARD_HEIGHT-4)
\r
7121 && gameInfo.variant != VariantXiangqi
\r
7122 && gameInfo.variant != VariantBerolina
\r
7123 && (board[fromY][fromX] == WhitePawn)
\r
7124 && (board[toY][toX] == EmptySquare)) {
\r
7125 board[fromY][fromX] = EmptySquare;
\r
7126 board[toY][toX] = WhitePawn;
\r
7127 captured = board[toY - 1][toX];
\r
7128 board[toY - 1][toX] = EmptySquare;
\r
7129 } else if ((fromY == BOARD_HEIGHT-4)
\r
7131 && gameInfo.variant == VariantBerolina
\r
7132 && (board[fromY][fromX] == WhitePawn)
\r
7133 && (board[toY][toX] == EmptySquare)) {
\r
7134 board[fromY][fromX] = EmptySquare;
\r
7135 board[toY][toX] = WhitePawn;
\r
7136 if(oldEP & EP_BEROLIN_A) {
\r
7137 captured = board[fromY][fromX-1];
\r
7138 board[fromY][fromX-1] = EmptySquare;
\r
7139 }else{ captured = board[fromY][fromX+1];
\r
7140 board[fromY][fromX+1] = EmptySquare;
\r
7142 } else if (board[fromY][fromX] == king
\r
7143 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7144 && toY == fromY && toX > fromX+1) {
\r
7145 board[fromY][fromX] = EmptySquare;
\r
7146 board[toY][toX] = king;
\r
7147 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7148 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7149 } else if (board[fromY][fromX] == king
\r
7150 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7151 && toY == fromY && toX < fromX-1) {
\r
7152 board[fromY][fromX] = EmptySquare;
\r
7153 board[toY][toX] = king;
\r
7154 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7155 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7156 } else if (fromY == 7 && fromX == 3
\r
7157 && board[fromY][fromX] == BlackKing
\r
7158 && toY == 7 && toX == 5) {
\r
7159 board[fromY][fromX] = EmptySquare;
\r
7160 board[toY][toX] = BlackKing;
\r
7161 board[fromY][7] = EmptySquare;
\r
7162 board[toY][4] = BlackRook;
\r
7163 } else if (fromY == 7 && fromX == 3
\r
7164 && board[fromY][fromX] == BlackKing
\r
7165 && toY == 7 && toX == 1) {
\r
7166 board[fromY][fromX] = EmptySquare;
\r
7167 board[toY][toX] = BlackKing;
\r
7168 board[fromY][0] = EmptySquare;
\r
7169 board[toY][2] = BlackRook;
\r
7170 } else if (board[fromY][fromX] == BlackPawn
\r
7172 && gameInfo.variant != VariantXiangqi
\r
7174 /* black pawn promotion */
\r
7175 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7176 if (board[0][toX] == EmptySquare) {
\r
7177 board[0][toX] = BlackQueen;
\r
7179 if(gameInfo.variant==VariantBughouse ||
\r
7180 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7181 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7182 board[fromY][fromX] = EmptySquare;
\r
7183 } else if ((fromY == 3)
\r
7185 && gameInfo.variant != VariantXiangqi
\r
7186 && gameInfo.variant != VariantBerolina
\r
7187 && (board[fromY][fromX] == BlackPawn)
\r
7188 && (board[toY][toX] == EmptySquare)) {
\r
7189 board[fromY][fromX] = EmptySquare;
\r
7190 board[toY][toX] = BlackPawn;
\r
7191 captured = board[toY + 1][toX];
\r
7192 board[toY + 1][toX] = EmptySquare;
\r
7193 } else if ((fromY == 3)
\r
7195 && gameInfo.variant == VariantBerolina
\r
7196 && (board[fromY][fromX] == BlackPawn)
\r
7197 && (board[toY][toX] == EmptySquare)) {
\r
7198 board[fromY][fromX] = EmptySquare;
\r
7199 board[toY][toX] = BlackPawn;
\r
7200 if(oldEP & EP_BEROLIN_A) {
\r
7201 captured = board[fromY][fromX-1];
\r
7202 board[fromY][fromX-1] = EmptySquare;
\r
7203 }else{ captured = board[fromY][fromX+1];
\r
7204 board[fromY][fromX+1] = EmptySquare;
\r
7207 board[toY][toX] = board[fromY][fromX];
\r
7208 board[fromY][fromX] = EmptySquare;
\r
7211 /* [HGM] now we promote for Shogi, if needed */
\r
7212 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7213 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7216 if (gameInfo.holdingsWidth != 0) {
\r
7218 /* !!A lot more code needs to be written to support holdings */
\r
7219 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7220 /* penultimate board files, so they are automaticlly stored */
\r
7221 /* in the game history. */
\r
7222 if (fromY == DROP_RANK) {
\r
7223 /* Delete from holdings, by decreasing count */
\r
7224 /* and erasing image if necessary */
\r
7226 if(p < (int) BlackPawn) { /* white drop */
\r
7227 p -= (int)WhitePawn;
\r
7228 if(p >= gameInfo.holdingsSize) p = 0;
\r
7229 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7230 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7231 } else { /* black drop */
\r
7232 p -= (int)BlackPawn;
\r
7233 if(p >= gameInfo.holdingsSize) p = 0;
\r
7234 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7235 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7238 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7239 && gameInfo.variant != VariantBughouse ) {
\r
7240 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7241 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7242 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7243 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7245 p = (int) captured;
\r
7246 if (p >= (int) BlackPawn) {
\r
7247 p -= (int)BlackPawn;
\r
7248 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7249 /* in Shogi restore piece to its original first */
\r
7250 captured = (ChessSquare) (DEMOTED captured);
\r
7253 p = PieceToNumber((ChessSquare)p);
\r
7254 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7255 board[p][BOARD_WIDTH-2]++;
\r
7256 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7258 p -= (int)WhitePawn;
\r
7259 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7260 captured = (ChessSquare) (DEMOTED captured);
\r
7263 p = PieceToNumber((ChessSquare)p);
\r
7264 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7265 board[BOARD_HEIGHT-1-p][1]++;
\r
7266 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7270 } else if (gameInfo.variant == VariantAtomic) {
\r
7271 if (captured != EmptySquare) {
\r
7273 for (y = toY-1; y <= toY+1; y++) {
\r
7274 for (x = toX-1; x <= toX+1; x++) {
\r
7275 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7276 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7277 board[y][x] = EmptySquare;
\r
7281 board[toY][toX] = EmptySquare;
\r
7284 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7285 /* [HGM] Shogi promotions */
\r
7286 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7291 /* Updates forwardMostMove */
\r
7293 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7294 int fromX, fromY, toX, toY;
\r
7297 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7299 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7300 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7301 piece = boards[forwardMostMove][fromY][fromX];
\r
7302 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7303 if(gameInfo.variant == VariantKnightmate)
\r
7304 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7305 if(forwardMostMove == 0) {
\r
7306 if(blackPlaysFirst)
\r
7307 fprintf(serverMoves, "%s;", second.tidy);
\r
7308 fprintf(serverMoves, "%s;", first.tidy);
\r
7309 if(!blackPlaysFirst)
\r
7310 fprintf(serverMoves, "%s;", second.tidy);
\r
7311 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7312 lastLoadFlag = loadFlag;
\r
7313 // print base move
\r
7314 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7315 // print castling suffix
\r
7316 if( toY == fromY && piece == king ) {
\r
7318 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7320 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7323 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7324 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7325 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7327 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7328 // promotion suffix
\r
7329 if(promoChar != NULLCHAR)
\r
7330 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7332 fprintf(serverMoves, "/%d/%d",
\r
7333 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7334 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7335 else timeLeft = blackTimeRemaining/1000;
\r
7336 fprintf(serverMoves, "/%d", timeLeft);
\r
7338 fflush(serverMoves);
\r
7341 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7342 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7347 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7348 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7349 if (commentList[forwardMostMove+1] != NULL) {
\r
7350 free(commentList[forwardMostMove+1]);
\r
7351 commentList[forwardMostMove+1] = NULL;
\r
7353 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7354 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7355 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7356 gameInfo.result = GameUnfinished;
\r
7357 if (gameInfo.resultDetails != NULL) {
\r
7358 free(gameInfo.resultDetails);
\r
7359 gameInfo.resultDetails = NULL;
\r
7361 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7362 moveList[forwardMostMove - 1]);
\r
7363 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7364 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7365 fromY, fromX, toY, toX, promoChar,
\r
7366 parseList[forwardMostMove - 1]);
\r
7367 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7368 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7369 castlingRights[forwardMostMove]) ) {
\r
7371 case MT_STALEMATE:
\r
7375 if(gameInfo.variant != VariantShogi)
\r
7376 strcat(parseList[forwardMostMove - 1], "+");
\r
7378 case MT_CHECKMATE:
\r
7379 strcat(parseList[forwardMostMove - 1], "#");
\r
7382 if (appData.debugMode) {
\r
7383 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7388 /* Updates currentMove if not pausing */
\r
7390 ShowMove(fromX, fromY, toX, toY)
\r
7392 int instant = (gameMode == PlayFromGameFile) ?
\r
7393 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7394 if(appData.noGUI) return;
\r
7395 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7397 if (forwardMostMove == currentMove + 1) {
\r
7398 AnimateMove(boards[forwardMostMove - 1],
\r
7399 fromX, fromY, toX, toY);
\r
7401 if (appData.highlightLastMove) {
\r
7402 SetHighlights(fromX, fromY, toX, toY);
\r
7405 currentMove = forwardMostMove;
\r
7408 if (instant) return;
\r
7410 DisplayMove(currentMove - 1);
\r
7411 DrawPosition(FALSE, boards[currentMove]);
\r
7412 DisplayBothClocks();
\r
7413 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7416 void SendEgtPath(ChessProgramState *cps)
\r
7417 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7418 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7420 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7423 char c, *q = name+1, *r, *s;
\r
7425 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7426 while(*p && *p != ',') *q++ = *p++;
\r
7427 *q++ = ':'; *q = 0;
\r
7428 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7429 strcmp(name, ",nalimov:") == 0 ) {
\r
7430 // take nalimov path from the menu-changeable option first, if it is defined
\r
7431 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7432 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7434 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7435 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7436 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7437 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7438 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7439 c = *r; *r = 0; // temporarily null-terminate path info
\r
7440 *--q = 0; // strip of trailig ':' from name
\r
7441 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7443 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7445 if(*p == ',') p++; // read away comma to position for next format name
\r
7450 InitChessProgram(cps, setup)
\r
7451 ChessProgramState *cps;
\r
7452 int setup; /* [HGM] needed to setup FRC opening position */
\r
7454 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7455 if (appData.noChessProgram) return;
\r
7456 hintRequested = FALSE;
\r
7457 bookRequested = FALSE;
\r
7459 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7460 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7461 if(cps->memSize) { /* [HGM] memory */
\r
7462 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7463 SendToProgram(buf, cps);
\r
7465 SendEgtPath(cps); /* [HGM] EGT */
\r
7466 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7467 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7468 SendToProgram(buf, cps);
\r
7471 SendToProgram(cps->initString, cps);
\r
7472 if (gameInfo.variant != VariantNormal &&
\r
7473 gameInfo.variant != VariantLoadable
\r
7474 /* [HGM] also send variant if board size non-standard */
\r
7475 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7477 char *v = VariantName(gameInfo.variant);
\r
7478 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7479 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7480 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7481 DisplayFatalError(buf, 0, 1);
\r
7485 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7486 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7487 if( gameInfo.variant == VariantXiangqi )
\r
7488 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7489 if( gameInfo.variant == VariantShogi )
\r
7490 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7491 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7492 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7493 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7494 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7495 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7496 if( gameInfo.variant == VariantCourier )
\r
7497 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7498 if( gameInfo.variant == VariantSuper )
\r
7499 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7500 if( gameInfo.variant == VariantGreat )
\r
7501 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7504 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7505 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7506 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7507 if(StrStr(cps->variants, b) == NULL) {
\r
7508 // specific sized variant not known, check if general sizing allowed
\r
7509 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7510 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7511 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7512 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7513 DisplayFatalError(buf, 0, 1);
\r
7516 /* [HGM] here we really should compare with the maximum supported board size */
\r
7519 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7520 sprintf(buf, "variant %s\n", b);
\r
7521 SendToProgram(buf, cps);
\r
7523 currentlyInitializedVariant = gameInfo.variant;
\r
7525 /* [HGM] send opening position in FRC to first engine */
\r
7527 SendToProgram("force\n", cps);
\r
7528 SendBoard(cps, 0);
\r
7529 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7530 setboardSpoiledMachineBlack = 1;
\r
7533 if (cps->sendICS) {
\r
7534 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7535 SendToProgram(buf, cps);
\r
7537 cps->maybeThinking = FALSE;
\r
7538 cps->offeredDraw = 0;
\r
7539 if (!appData.icsActive) {
\r
7540 SendTimeControl(cps, movesPerSession, timeControl,
\r
7541 timeIncrement, appData.searchDepth,
\r
7544 if (appData.showThinking
\r
7545 // [HGM] thinking: four options require thinking output to be sent
\r
7546 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7548 SendToProgram("post\n", cps);
\r
7550 SendToProgram("hard\n", cps);
\r
7551 if (!appData.ponderNextMove) {
\r
7552 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7553 it without being sure what state we are in first. "hard"
\r
7554 is not a toggle, so that one is OK.
\r
7556 SendToProgram("easy\n", cps);
\r
7558 if (cps->usePing) {
\r
7559 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7560 SendToProgram(buf, cps);
\r
7562 cps->initDone = TRUE;
\r
7567 StartChessProgram(cps)
\r
7568 ChessProgramState *cps;
\r
7570 char buf[MSG_SIZ];
\r
7573 if (appData.noChessProgram) return;
\r
7574 cps->initDone = FALSE;
\r
7576 if (strcmp(cps->host, "localhost") == 0) {
\r
7577 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7578 } else if (*appData.remoteShell == NULLCHAR) {
\r
7579 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7581 if (*appData.remoteUser == NULLCHAR) {
\r
7582 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7585 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7586 cps->host, appData.remoteUser, cps->program);
\r
7588 err = StartChildProcess(buf, "", &cps->pr);
\r
7592 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7593 DisplayFatalError(buf, err, 1);
\r
7599 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7600 if (cps->protocolVersion > 1) {
\r
7601 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7602 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7603 cps->comboCnt = 0; // and values of combo boxes
\r
7604 SendToProgram(buf, cps);
\r
7606 SendToProgram("xboard\n", cps);
\r
7612 TwoMachinesEventIfReady P((void))
\r
7614 if (first.lastPing != first.lastPong) {
\r
7615 DisplayMessage("", _("Waiting for first chess program"));
\r
7616 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7619 if (second.lastPing != second.lastPong) {
\r
7620 DisplayMessage("", _("Waiting for second chess program"));
\r
7621 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7625 TwoMachinesEvent();
\r
7629 NextMatchGame P((void))
\r
7631 int index; /* [HGM] autoinc: step lod index during match */
\r
7632 Reset(FALSE, TRUE);
\r
7633 if (*appData.loadGameFile != NULLCHAR) {
\r
7634 index = appData.loadGameIndex;
\r
7635 if(index < 0) { // [HGM] autoinc
\r
7636 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7637 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7639 LoadGameFromFile(appData.loadGameFile,
\r
7641 appData.loadGameFile, FALSE);
\r
7642 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7643 index = appData.loadPositionIndex;
\r
7644 if(index < 0) { // [HGM] autoinc
\r
7645 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7646 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7648 LoadPositionFromFile(appData.loadPositionFile,
\r
7650 appData.loadPositionFile);
\r
7652 TwoMachinesEventIfReady();
\r
7655 void UserAdjudicationEvent( int result )
\r
7657 ChessMove gameResult = GameIsDrawn;
\r
7659 if( result > 0 ) {
\r
7660 gameResult = WhiteWins;
\r
7662 else if( result < 0 ) {
\r
7663 gameResult = BlackWins;
\r
7666 if( gameMode == TwoMachinesPlay ) {
\r
7667 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7673 GameEnds(result, resultDetails, whosays)
\r
7675 char *resultDetails;
\r
7678 GameMode nextGameMode;
\r
7680 char buf[MSG_SIZ];
\r
7682 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7685 if (appData.debugMode) {
\r
7686 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7687 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7690 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7691 /* If we are playing on ICS, the server decides when the
\r
7692 game is over, but the engine can offer to draw, claim
\r
7693 a draw, or resign.
\r
7696 if (appData.zippyPlay && first.initDone) {
\r
7697 if (result == GameIsDrawn) {
\r
7698 /* In case draw still needs to be claimed */
\r
7699 SendToICS(ics_prefix);
\r
7700 SendToICS("draw\n");
\r
7701 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7702 SendToICS(ics_prefix);
\r
7703 SendToICS("resign\n");
\r
7707 endingGame = 0; /* [HGM] crash */
\r
7711 /* If we're loading the game from a file, stop */
\r
7712 if (whosays == GE_FILE) {
\r
7713 (void) StopLoadGameTimer();
\r
7714 gameFileFP = NULL;
\r
7717 /* Cancel draw offers */
\r
7718 first.offeredDraw = second.offeredDraw = 0;
\r
7720 /* If this is an ICS game, only ICS can really say it's done;
\r
7721 if not, anyone can. */
\r
7722 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7723 gameMode == IcsPlayingBlack ||
\r
7724 gameMode == IcsObserving ||
\r
7725 gameMode == IcsExamining);
\r
7727 if (!isIcsGame || whosays == GE_ICS) {
\r
7728 /* OK -- not an ICS game, or ICS said it was done */
\r
7730 if (!isIcsGame && !appData.noChessProgram)
\r
7731 SetUserThinkingEnables();
\r
7733 /* [HGM] if a machine claims the game end we verify this claim */
\r
7734 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7735 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7737 ChessMove trueResult = (ChessMove) -1;
\r
7739 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7740 first.twoMachinesColor[0] :
\r
7741 second.twoMachinesColor[0] ;
\r
7743 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
\r
7744 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
\r
7745 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7746 trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)
\r
7747 ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!
\r
7749 if(epStatus[forwardMostMove] == EP_STALEMATE) {
\r
7750 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
\r
7751 if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide ||
\r
7752 gameInfo.variant == VariantLosers) // [HGM] losers: in giveaway variants stalemate wins
\r
7753 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
\r
7756 // now verify win claims, but not in drop games, as we don't understand those yet
\r
7757 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
\r
7758 || gameInfo.variant == VariantGreat) &&
\r
7759 (result == WhiteWins && claimer == 'w' ||
\r
7760 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
\r
7761 if (appData.debugMode) {
\r
7762 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7763 result, epStatus[forwardMostMove], forwardMostMove);
\r
7765 if(result != trueResult) {
\r
7766 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7767 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7768 resultDetails = buf;
\r
7771 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7772 && (forwardMostMove <= backwardMostMove ||
\r
7773 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7774 (claimer=='b')==(forwardMostMove&1))
\r
7776 /* [HGM] verify: draws that were not flagged are false claims */
\r
7777 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7778 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7779 resultDetails = buf;
\r
7781 /* (Claiming a loss is accepted no questions asked!) */
\r
7783 /* [HGM] bare: don't allow bare King to win */
\r
7784 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7785 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7786 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7787 && result != GameIsDrawn)
\r
7788 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7789 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7790 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7791 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7793 if (appData.debugMode) {
\r
7794 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7795 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7798 result = GameIsDrawn;
\r
7799 sprintf(buf, "%s but bare king", resultDetails);
\r
7800 resultDetails = buf;
\r
7806 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7807 if(result==WhiteWins) c = '+';
\r
7808 if(result==BlackWins) c = '-';
\r
7809 if(resultDetails != NULL)
\r
7810 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7812 if (resultDetails != NULL) {
\r
7813 gameInfo.result = result;
\r
7814 gameInfo.resultDetails = StrSave(resultDetails);
\r
7816 /* display last move only if game was not loaded from file */
\r
7817 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7818 DisplayMove(currentMove - 1);
\r
7820 if (forwardMostMove != 0) {
\r
7821 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7822 if (*appData.saveGameFile != NULLCHAR) {
\r
7823 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7824 } else if (appData.autoSaveGames) {
\r
7827 if (*appData.savePositionFile != NULLCHAR) {
\r
7828 SavePositionToFile(appData.savePositionFile);
\r
7833 /* Tell program how game ended in case it is learning */
\r
7834 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7835 /* engine died and we got here through time loss. In that */
\r
7836 /* case we will get a fatal error writing the pipe, which */
\r
7837 /* would otherwise lose us the PGN. */
\r
7838 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7839 /* output during GameEnds should never be fatal anymore */
\r
7840 if (gameMode == MachinePlaysWhite ||
\r
7841 gameMode == MachinePlaysBlack ||
\r
7842 gameMode == TwoMachinesPlay ||
\r
7843 gameMode == IcsPlayingWhite ||
\r
7844 gameMode == IcsPlayingBlack ||
\r
7845 gameMode == BeginningOfGame) {
\r
7846 char buf[MSG_SIZ];
\r
7847 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7849 if (first.pr != NoProc) {
\r
7850 SendToProgram(buf, &first);
\r
7852 if (second.pr != NoProc &&
\r
7853 gameMode == TwoMachinesPlay) {
\r
7854 SendToProgram(buf, &second);
\r
7859 if (appData.icsActive) {
\r
7860 if (appData.quietPlay &&
\r
7861 (gameMode == IcsPlayingWhite ||
\r
7862 gameMode == IcsPlayingBlack)) {
\r
7863 SendToICS(ics_prefix);
\r
7864 SendToICS("set shout 1\n");
\r
7866 nextGameMode = IcsIdle;
\r
7867 ics_user_moved = FALSE;
\r
7868 /* clean up premove. It's ugly when the game has ended and the
\r
7869 * premove highlights are still on the board.
\r
7872 gotPremove = FALSE;
\r
7873 ClearPremoveHighlights();
\r
7874 DrawPosition(FALSE, boards[currentMove]);
\r
7876 if (whosays == GE_ICS) {
\r
7879 if (gameMode == IcsPlayingWhite)
\r
7880 PlayIcsWinSound();
\r
7881 else if(gameMode == IcsPlayingBlack)
\r
7882 PlayIcsLossSound();
\r
7885 if (gameMode == IcsPlayingBlack)
\r
7886 PlayIcsWinSound();
\r
7887 else if(gameMode == IcsPlayingWhite)
\r
7888 PlayIcsLossSound();
\r
7891 PlayIcsDrawSound();
\r
7894 PlayIcsUnfinishedSound();
\r
7897 } else if (gameMode == EditGame ||
\r
7898 gameMode == PlayFromGameFile ||
\r
7899 gameMode == AnalyzeMode ||
\r
7900 gameMode == AnalyzeFile) {
\r
7901 nextGameMode = gameMode;
\r
7903 nextGameMode = EndOfGame;
\r
7908 nextGameMode = gameMode;
\r
7911 if (appData.noChessProgram) {
\r
7912 gameMode = nextGameMode;
\r
7914 endingGame = 0; /* [HGM] crash */
\r
7918 if (first.reuse) {
\r
7919 /* Put first chess program into idle state */
\r
7920 if (first.pr != NoProc &&
\r
7921 (gameMode == MachinePlaysWhite ||
\r
7922 gameMode == MachinePlaysBlack ||
\r
7923 gameMode == TwoMachinesPlay ||
\r
7924 gameMode == IcsPlayingWhite ||
\r
7925 gameMode == IcsPlayingBlack ||
\r
7926 gameMode == BeginningOfGame)) {
\r
7927 SendToProgram("force\n", &first);
\r
7928 if (first.usePing) {
\r
7929 char buf[MSG_SIZ];
\r
7930 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7931 SendToProgram(buf, &first);
\r
7934 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7935 /* Kill off first chess program */
\r
7936 if (first.isr != NULL)
\r
7937 RemoveInputSource(first.isr);
\r
7940 if (first.pr != NoProc) {
\r
7941 ExitAnalyzeMode();
\r
7942 DoSleep( appData.delayBeforeQuit );
\r
7943 SendToProgram("quit\n", &first);
\r
7944 DoSleep( appData.delayAfterQuit );
\r
7945 DestroyChildProcess(first.pr, first.useSigterm);
\r
7947 first.pr = NoProc;
\r
7949 if (second.reuse) {
\r
7950 /* Put second chess program into idle state */
\r
7951 if (second.pr != NoProc &&
\r
7952 gameMode == TwoMachinesPlay) {
\r
7953 SendToProgram("force\n", &second);
\r
7954 if (second.usePing) {
\r
7955 char buf[MSG_SIZ];
\r
7956 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7957 SendToProgram(buf, &second);
\r
7960 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7961 /* Kill off second chess program */
\r
7962 if (second.isr != NULL)
\r
7963 RemoveInputSource(second.isr);
\r
7964 second.isr = NULL;
\r
7966 if (second.pr != NoProc) {
\r
7967 DoSleep( appData.delayBeforeQuit );
\r
7968 SendToProgram("quit\n", &second);
\r
7969 DoSleep( appData.delayAfterQuit );
\r
7970 DestroyChildProcess(second.pr, second.useSigterm);
\r
7972 second.pr = NoProc;
\r
7975 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7978 if (first.twoMachinesColor[0] == 'w') {
\r
7979 first.matchWins++;
\r
7981 second.matchWins++;
\r
7985 if (first.twoMachinesColor[0] == 'b') {
\r
7986 first.matchWins++;
\r
7988 second.matchWins++;
\r
7994 if (matchGame < appData.matchGames) {
\r
7996 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
7997 tmp = first.twoMachinesColor;
\r
7998 first.twoMachinesColor = second.twoMachinesColor;
\r
7999 second.twoMachinesColor = tmp;
\r
8001 gameMode = nextGameMode;
\r
8003 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8004 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8005 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8006 endingGame = 0; /* [HGM] crash */
\r
8009 char buf[MSG_SIZ];
\r
8010 gameMode = nextGameMode;
\r
8011 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8012 first.tidy, second.tidy,
\r
8013 first.matchWins, second.matchWins,
\r
8014 appData.matchGames - (first.matchWins + second.matchWins));
\r
8015 DisplayFatalError(buf, 0, 0);
\r
8018 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8019 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8020 ExitAnalyzeMode();
\r
8021 gameMode = nextGameMode;
\r
8023 endingGame = 0; /* [HGM] crash */
\r
8026 /* Assumes program was just initialized (initString sent).
\r
8027 Leaves program in force mode. */
\r
8029 FeedMovesToProgram(cps, upto)
\r
8030 ChessProgramState *cps;
\r
8035 if (appData.debugMode)
\r
8036 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8037 startedFromSetupPosition ? "position and " : "",
\r
8038 backwardMostMove, upto, cps->which);
\r
8039 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8040 // [HGM] variantswitch: make engine aware of new variant
\r
8041 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8042 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8043 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8044 SendToProgram(buf, cps);
\r
8045 currentlyInitializedVariant = gameInfo.variant;
\r
8047 SendToProgram("force\n", cps);
\r
8048 if (startedFromSetupPosition) {
\r
8049 SendBoard(cps, backwardMostMove);
\r
8050 if (appData.debugMode) {
\r
8051 fprintf(debugFP, "feedMoves\n");
\r
8054 for (i = backwardMostMove; i < upto; i++) {
\r
8055 SendMoveToProgram(i, cps);
\r
8061 ResurrectChessProgram()
\r
8063 /* The chess program may have exited.
\r
8064 If so, restart it and feed it all the moves made so far. */
\r
8066 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8068 StartChessProgram(&first);
\r
8069 InitChessProgram(&first, FALSE);
\r
8070 FeedMovesToProgram(&first, currentMove);
\r
8072 if (!first.sendTime) {
\r
8073 /* can't tell gnuchess what its clock should read,
\r
8074 so we bow to its notion. */
\r
8076 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8077 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8080 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8081 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8082 SendToProgram("analyze\n", &first);
\r
8083 first.analyzing = TRUE;
\r
8088 * Button procedures
\r
8091 Reset(redraw, init)
\r
8096 if (appData.debugMode) {
\r
8097 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8098 redraw, init, gameMode);
\r
8100 pausing = pauseExamInvalid = FALSE;
\r
8101 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8103 whiteFlag = blackFlag = FALSE;
\r
8104 userOfferedDraw = FALSE;
\r
8105 hintRequested = bookRequested = FALSE;
\r
8106 first.maybeThinking = FALSE;
\r
8107 second.maybeThinking = FALSE;
\r
8108 first.bookSuspend = FALSE; // [HGM] book
\r
8109 second.bookSuspend = FALSE;
\r
8110 thinkOutput[0] = NULLCHAR;
\r
8111 lastHint[0] = NULLCHAR;
\r
8112 ClearGameInfo(&gameInfo);
\r
8113 gameInfo.variant = StringToVariant(appData.variant);
\r
8114 ics_user_moved = ics_clock_paused = FALSE;
\r
8115 ics_getting_history = H_FALSE;
\r
8117 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8118 ClearProgramStats();
\r
8119 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8122 ClearHighlights();
\r
8123 flipView = appData.flipView;
\r
8124 ClearPremoveHighlights();
\r
8125 gotPremove = FALSE;
\r
8126 alarmSounded = FALSE;
\r
8128 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8129 if(appData.serverMovesName != NULL) {
\r
8130 /* [HGM] prepare to make moves file for broadcasting */
\r
8131 clock_t t = clock();
\r
8132 if(serverMoves != NULL) fclose(serverMoves);
\r
8133 serverMoves = fopen(appData.serverMovesName, "r");
\r
8134 if(serverMoves != NULL) {
\r
8135 fclose(serverMoves);
\r
8136 /* delay 15 sec before overwriting, so all clients can see end */
\r
8137 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8139 serverMoves = fopen(appData.serverMovesName, "w");
\r
8142 ExitAnalyzeMode();
\r
8143 gameMode = BeginningOfGame;
\r
8145 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8146 InitPosition(redraw);
\r
8147 for (i = 0; i < MAX_MOVES; i++) {
\r
8148 if (commentList[i] != NULL) {
\r
8149 free(commentList[i]);
\r
8150 commentList[i] = NULL;
\r
8154 timeRemaining[0][0] = whiteTimeRemaining;
\r
8155 timeRemaining[1][0] = blackTimeRemaining;
\r
8156 if (first.pr == NULL) {
\r
8157 StartChessProgram(&first);
\r
8160 InitChessProgram(&first, startedFromSetupPosition);
\r
8163 DisplayMessage("", "");
\r
8164 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8168 AutoPlayGameLoop()
\r
8171 if (!AutoPlayOneMove())
\r
8173 if (matchMode || appData.timeDelay == 0)
\r
8175 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8177 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8186 int fromX, fromY, toX, toY;
\r
8188 if (appData.debugMode) {
\r
8189 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8192 if (gameMode != PlayFromGameFile)
\r
8195 if (currentMove >= forwardMostMove) {
\r
8196 gameMode = EditGame;
\r
8199 /* [AS] Clear current move marker at the end of a game */
\r
8200 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8205 toX = moveList[currentMove][2] - AAA;
\r
8206 toY = moveList[currentMove][3] - ONE;
\r
8208 if (moveList[currentMove][1] == '@') {
\r
8209 if (appData.highlightLastMove) {
\r
8210 SetHighlights(-1, -1, toX, toY);
\r
8213 fromX = moveList[currentMove][0] - AAA;
\r
8214 fromY = moveList[currentMove][1] - ONE;
\r
8216 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8218 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8220 if (appData.highlightLastMove) {
\r
8221 SetHighlights(fromX, fromY, toX, toY);
\r
8224 DisplayMove(currentMove);
\r
8225 SendMoveToProgram(currentMove++, &first);
\r
8226 DisplayBothClocks();
\r
8227 DrawPosition(FALSE, boards[currentMove]);
\r
8228 // [HGM] PV info: always display, routine tests if empty
\r
8229 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8235 LoadGameOneMove(readAhead)
\r
8236 ChessMove readAhead;
\r
8238 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8239 char promoChar = NULLCHAR;
\r
8240 ChessMove moveType;
\r
8241 char move[MSG_SIZ];
\r
8244 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8245 gameMode != AnalyzeMode && gameMode != Training) {
\r
8246 gameFileFP = NULL;
\r
8250 yyboardindex = forwardMostMove;
\r
8251 if (readAhead != (ChessMove)0) {
\r
8252 moveType = readAhead;
\r
8254 if (gameFileFP == NULL)
\r
8256 moveType = (ChessMove) yylex();
\r
8260 switch (moveType) {
\r
8262 if (appData.debugMode)
\r
8263 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8265 if (*p == '{' || *p == '[' || *p == '(') {
\r
8266 p[strlen(p) - 1] = NULLCHAR;
\r
8270 /* append the comment but don't display it */
\r
8271 while (*p == '\n') p++;
\r
8272 AppendComment(currentMove, p);
\r
8275 case WhiteCapturesEnPassant:
\r
8276 case BlackCapturesEnPassant:
\r
8277 case WhitePromotionChancellor:
\r
8278 case BlackPromotionChancellor:
\r
8279 case WhitePromotionArchbishop:
\r
8280 case BlackPromotionArchbishop:
\r
8281 case WhitePromotionCentaur:
\r
8282 case BlackPromotionCentaur:
\r
8283 case WhitePromotionQueen:
\r
8284 case BlackPromotionQueen:
\r
8285 case WhitePromotionRook:
\r
8286 case BlackPromotionRook:
\r
8287 case WhitePromotionBishop:
\r
8288 case BlackPromotionBishop:
\r
8289 case WhitePromotionKnight:
\r
8290 case BlackPromotionKnight:
\r
8291 case WhitePromotionKing:
\r
8292 case BlackPromotionKing:
\r
8294 case WhiteKingSideCastle:
\r
8295 case WhiteQueenSideCastle:
\r
8296 case BlackKingSideCastle:
\r
8297 case BlackQueenSideCastle:
\r
8298 case WhiteKingSideCastleWild:
\r
8299 case WhiteQueenSideCastleWild:
\r
8300 case BlackKingSideCastleWild:
\r
8301 case BlackQueenSideCastleWild:
\r
8303 case WhiteHSideCastleFR:
\r
8304 case WhiteASideCastleFR:
\r
8305 case BlackHSideCastleFR:
\r
8306 case BlackASideCastleFR:
\r
8308 if (appData.debugMode)
\r
8309 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8310 fromX = currentMoveString[0] - AAA;
\r
8311 fromY = currentMoveString[1] - ONE;
\r
8312 toX = currentMoveString[2] - AAA;
\r
8313 toY = currentMoveString[3] - ONE;
\r
8314 promoChar = currentMoveString[4];
\r
8319 if (appData.debugMode)
\r
8320 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8321 fromX = moveType == WhiteDrop ?
\r
8322 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8323 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8324 fromY = DROP_RANK;
\r
8325 toX = currentMoveString[2] - AAA;
\r
8326 toY = currentMoveString[3] - ONE;
\r
8332 case GameUnfinished:
\r
8333 if (appData.debugMode)
\r
8334 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8335 p = strchr(yy_text, '{');
\r
8336 if (p == NULL) p = strchr(yy_text, '(');
\r
8339 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8341 q = strchr(p, *p == '{' ? '}' : ')');
\r
8342 if (q != NULL) *q = NULLCHAR;
\r
8345 GameEnds(moveType, p, GE_FILE);
\r
8347 if (cmailMsgLoaded) {
\r
8348 ClearHighlights();
\r
8349 flipView = WhiteOnMove(currentMove);
\r
8350 if (moveType == GameUnfinished) flipView = !flipView;
\r
8351 if (appData.debugMode)
\r
8352 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8356 case (ChessMove) 0: /* end of file */
\r
8357 if (appData.debugMode)
\r
8358 fprintf(debugFP, "Parser hit end of file\n");
\r
8359 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8360 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8364 case MT_CHECKMATE:
\r
8365 if (WhiteOnMove(currentMove)) {
\r
8366 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8368 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8371 case MT_STALEMATE:
\r
8372 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8378 case MoveNumberOne:
\r
8379 if (lastLoadGameStart == GNUChessGame) {
\r
8380 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8381 if (appData.debugMode)
\r
8382 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8383 yy_text, (int) moveType);
\r
8384 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8386 /* else fall thru */
\r
8389 case GNUChessGame:
\r
8391 /* Reached start of next game in file */
\r
8392 if (appData.debugMode)
\r
8393 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8394 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8395 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8399 case MT_CHECKMATE:
\r
8400 if (WhiteOnMove(currentMove)) {
\r
8401 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8403 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8406 case MT_STALEMATE:
\r
8407 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8413 case PositionDiagram: /* should not happen; ignore */
\r
8414 case ElapsedTime: /* ignore */
\r
8415 case NAG: /* ignore */
\r
8416 if (appData.debugMode)
\r
8417 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8418 yy_text, (int) moveType);
\r
8419 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8422 if (appData.testLegality) {
\r
8423 if (appData.debugMode)
\r
8424 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8425 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8426 (forwardMostMove / 2) + 1,
\r
8427 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8428 DisplayError(move, 0);
\r
8431 if (appData.debugMode)
\r
8432 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8433 yy_text, currentMoveString);
\r
8434 fromX = currentMoveString[0] - AAA;
\r
8435 fromY = currentMoveString[1] - ONE;
\r
8436 toX = currentMoveString[2] - AAA;
\r
8437 toY = currentMoveString[3] - ONE;
\r
8438 promoChar = currentMoveString[4];
\r
8442 case AmbiguousMove:
\r
8443 if (appData.debugMode)
\r
8444 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8445 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8446 (forwardMostMove / 2) + 1,
\r
8447 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8448 DisplayError(move, 0);
\r
8453 case ImpossibleMove:
\r
8454 if (appData.debugMode)
\r
8455 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8456 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8457 (forwardMostMove / 2) + 1,
\r
8458 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8459 DisplayError(move, 0);
\r
8465 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8466 DrawPosition(FALSE, boards[currentMove]);
\r
8467 DisplayBothClocks();
\r
8468 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8469 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8471 (void) StopLoadGameTimer();
\r
8472 gameFileFP = NULL;
\r
8473 cmailOldMove = forwardMostMove;
\r
8476 /* currentMoveString is set as a side-effect of yylex */
\r
8477 strcat(currentMoveString, "\n");
\r
8478 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8480 thinkOutput[0] = NULLCHAR;
\r
8481 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8482 currentMove = forwardMostMove;
\r
8487 /* Load the nth game from the given file */
\r
8489 LoadGameFromFile(filename, n, title, useList)
\r
8493 /*Boolean*/ int useList;
\r
8496 char buf[MSG_SIZ];
\r
8498 if (strcmp(filename, "-") == 0) {
\r
8502 f = fopen(filename, "rb");
\r
8504 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8505 DisplayError(buf, errno);
\r
8509 if (fseek(f, 0, 0) == -1) {
\r
8510 /* f is not seekable; probably a pipe */
\r
8513 if (useList && n == 0) {
\r
8514 int error = GameListBuild(f);
\r
8516 DisplayError(_("Cannot build game list"), error);
\r
8517 } else if (!ListEmpty(&gameList) &&
\r
8518 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8519 GameListPopUp(f, title);
\r
8522 GameListDestroy();
\r
8525 if (n == 0) n = 1;
\r
8526 return LoadGame(f, n, title, FALSE);
\r
8531 MakeRegisteredMove()
\r
8533 int fromX, fromY, toX, toY;
\r
8535 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8536 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8539 if (appData.debugMode)
\r
8540 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8541 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8543 thinkOutput[0] = NULLCHAR;
\r
8544 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8545 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8546 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8547 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8548 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8549 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8550 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8551 ShowMove(fromX, fromY, toX, toY);
\r
8553 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8554 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8559 case MT_CHECKMATE:
\r
8560 if (WhiteOnMove(currentMove)) {
\r
8561 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8563 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8567 case MT_STALEMATE:
\r
8568 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8574 case CMAIL_RESIGN:
\r
8575 if (WhiteOnMove(currentMove)) {
\r
8576 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8578 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8582 case CMAIL_ACCEPT:
\r
8583 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8594 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8596 CmailLoadGame(f, gameNumber, title, useList)
\r
8604 if (gameNumber > nCmailGames) {
\r
8605 DisplayError(_("No more games in this message"), 0);
\r
8608 if (f == lastLoadGameFP) {
\r
8609 int offset = gameNumber - lastLoadGameNumber;
\r
8610 if (offset == 0) {
\r
8611 cmailMsg[0] = NULLCHAR;
\r
8612 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8613 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8614 nCmailMovesRegistered--;
\r
8616 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8617 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8618 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8621 if (! RegisterMove()) return FALSE;
\r
8625 retVal = LoadGame(f, gameNumber, title, useList);
\r
8627 /* Make move registered during previous look at this game, if any */
\r
8628 MakeRegisteredMove();
\r
8630 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8631 commentList[currentMove]
\r
8632 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8633 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8639 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8641 ReloadGame(offset)
\r
8644 int gameNumber = lastLoadGameNumber + offset;
\r
8645 if (lastLoadGameFP == NULL) {
\r
8646 DisplayError(_("No game has been loaded yet"), 0);
\r
8649 if (gameNumber <= 0) {
\r
8650 DisplayError(_("Can't back up any further"), 0);
\r
8653 if (cmailMsgLoaded) {
\r
8654 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8655 lastLoadGameTitle, lastLoadGameUseList);
\r
8657 return LoadGame(lastLoadGameFP, gameNumber,
\r
8658 lastLoadGameTitle, lastLoadGameUseList);
\r
8664 /* Load the nth game from open file f */
\r
8666 LoadGame(f, gameNumber, title, useList)
\r
8673 char buf[MSG_SIZ];
\r
8674 int gn = gameNumber;
\r
8675 ListGame *lg = NULL;
\r
8676 int numPGNTags = 0;
\r
8678 GameMode oldGameMode;
\r
8679 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8681 if (appData.debugMode)
\r
8682 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8684 if (gameMode == Training )
\r
8685 SetTrainingModeOff();
\r
8687 oldGameMode = gameMode;
\r
8688 if (gameMode != BeginningOfGame) {
\r
8689 Reset(FALSE, TRUE);
\r
8693 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8694 fclose(lastLoadGameFP);
\r
8698 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8701 fseek(f, lg->offset, 0);
\r
8702 GameListHighlight(gameNumber);
\r
8706 DisplayError(_("Game number out of range"), 0);
\r
8710 GameListDestroy();
\r
8711 if (fseek(f, 0, 0) == -1) {
\r
8712 if (f == lastLoadGameFP ?
\r
8713 gameNumber == lastLoadGameNumber + 1 :
\r
8714 gameNumber == 1) {
\r
8717 DisplayError(_("Can't seek on game file"), 0);
\r
8722 lastLoadGameFP = f;
\r
8723 lastLoadGameNumber = gameNumber;
\r
8724 strcpy(lastLoadGameTitle, title);
\r
8725 lastLoadGameUseList = useList;
\r
8729 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8730 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8731 lg->gameInfo.black);
\r
8732 DisplayTitle(buf);
\r
8733 } else if (*title != NULLCHAR) {
\r
8734 if (gameNumber > 1) {
\r
8735 sprintf(buf, "%s %d", title, gameNumber);
\r
8736 DisplayTitle(buf);
\r
8738 DisplayTitle(title);
\r
8742 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8743 gameMode = PlayFromGameFile;
\r
8747 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8748 CopyBoard(boards[0], initialPosition);
\r
8752 * Skip the first gn-1 games in the file.
\r
8753 * Also skip over anything that precedes an identifiable
\r
8754 * start of game marker, to avoid being confused by
\r
8755 * garbage at the start of the file. Currently
\r
8756 * recognized start of game markers are the move number "1",
\r
8757 * the pattern "gnuchess .* game", the pattern
\r
8758 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8759 * A game that starts with one of the latter two patterns
\r
8760 * will also have a move number 1, possibly
\r
8761 * following a position diagram.
\r
8762 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8763 * start with an unnumbered move. Does that break anything?
\r
8765 cm = lastLoadGameStart = (ChessMove) 0;
\r
8767 yyboardindex = forwardMostMove;
\r
8768 cm = (ChessMove) yylex();
\r
8770 case (ChessMove) 0:
\r
8771 if (cmailMsgLoaded) {
\r
8772 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8774 Reset(TRUE, TRUE);
\r
8775 DisplayError(_("Game not found in file"), 0);
\r
8779 case GNUChessGame:
\r
8782 lastLoadGameStart = cm;
\r
8785 case MoveNumberOne:
\r
8786 switch (lastLoadGameStart) {
\r
8787 case GNUChessGame:
\r
8791 case MoveNumberOne:
\r
8792 case (ChessMove) 0:
\r
8793 gn--; /* count this game */
\r
8794 lastLoadGameStart = cm;
\r
8803 switch (lastLoadGameStart) {
\r
8804 case GNUChessGame:
\r
8806 case MoveNumberOne:
\r
8807 case (ChessMove) 0:
\r
8808 gn--; /* count this game */
\r
8809 lastLoadGameStart = cm;
\r
8812 lastLoadGameStart = cm; /* game counted already */
\r
8820 yyboardindex = forwardMostMove;
\r
8821 cm = (ChessMove) yylex();
\r
8822 } while (cm == PGNTag || cm == Comment);
\r
8829 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8830 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8831 != CMAIL_OLD_RESULT) {
\r
8832 nCmailResults ++ ;
\r
8833 cmailResult[ CMAIL_MAX_GAMES
\r
8834 - gn - 1] = CMAIL_OLD_RESULT;
\r
8840 /* Only a NormalMove can be at the start of a game
\r
8841 * without a position diagram. */
\r
8842 if (lastLoadGameStart == (ChessMove) 0) {
\r
8844 lastLoadGameStart = MoveNumberOne;
\r
8853 if (appData.debugMode)
\r
8854 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8856 if (cm == XBoardGame) {
\r
8857 /* Skip any header junk before position diagram and/or move 1 */
\r
8859 yyboardindex = forwardMostMove;
\r
8860 cm = (ChessMove) yylex();
\r
8862 if (cm == (ChessMove) 0 ||
\r
8863 cm == GNUChessGame || cm == XBoardGame) {
\r
8864 /* Empty game; pretend end-of-file and handle later */
\r
8865 cm = (ChessMove) 0;
\r
8869 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8870 cm == PGNTag || cm == Comment)
\r
8873 } else if (cm == GNUChessGame) {
\r
8874 if (gameInfo.event != NULL) {
\r
8875 free(gameInfo.event);
\r
8877 gameInfo.event = StrSave(yy_text);
\r
8880 startedFromSetupPosition = FALSE;
\r
8881 while (cm == PGNTag) {
\r
8882 if (appData.debugMode)
\r
8883 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8884 err = ParsePGNTag(yy_text, &gameInfo);
\r
8885 if (!err) numPGNTags++;
\r
8887 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8888 if(gameInfo.variant != oldVariant) {
\r
8889 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8890 InitPosition(TRUE);
\r
8891 oldVariant = gameInfo.variant;
\r
8892 if (appData.debugMode)
\r
8893 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8897 if (gameInfo.fen != NULL) {
\r
8898 Board initial_position;
\r
8899 startedFromSetupPosition = TRUE;
\r
8900 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8901 Reset(TRUE, TRUE);
\r
8902 DisplayError(_("Bad FEN position in file"), 0);
\r
8905 CopyBoard(boards[0], initial_position);
\r
8906 if (blackPlaysFirst) {
\r
8907 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8908 CopyBoard(boards[1], initial_position);
\r
8909 strcpy(moveList[0], "");
\r
8910 strcpy(parseList[0], "");
\r
8911 timeRemaining[0][1] = whiteTimeRemaining;
\r
8912 timeRemaining[1][1] = blackTimeRemaining;
\r
8913 if (commentList[0] != NULL) {
\r
8914 commentList[1] = commentList[0];
\r
8915 commentList[0] = NULL;
\r
8918 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8920 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8922 initialRulePlies = FENrulePlies;
\r
8923 epStatus[forwardMostMove] = FENepStatus;
\r
8924 for( i=0; i< nrCastlingRights; i++ )
\r
8925 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8927 yyboardindex = forwardMostMove;
\r
8928 free(gameInfo.fen);
\r
8929 gameInfo.fen = NULL;
\r
8932 yyboardindex = forwardMostMove;
\r
8933 cm = (ChessMove) yylex();
\r
8935 /* Handle comments interspersed among the tags */
\r
8936 while (cm == Comment) {
\r
8938 if (appData.debugMode)
\r
8939 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8941 if (*p == '{' || *p == '[' || *p == '(') {
\r
8942 p[strlen(p) - 1] = NULLCHAR;
\r
8945 while (*p == '\n') p++;
\r
8946 AppendComment(currentMove, p);
\r
8947 yyboardindex = forwardMostMove;
\r
8948 cm = (ChessMove) yylex();
\r
8952 /* don't rely on existence of Event tag since if game was
\r
8953 * pasted from clipboard the Event tag may not exist
\r
8955 if (numPGNTags > 0){
\r
8957 if (gameInfo.variant == VariantNormal) {
\r
8958 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8961 if( appData.autoDisplayTags ) {
\r
8962 tags = PGNTags(&gameInfo);
\r
8963 TagsPopUp(tags, CmailMsg());
\r
8968 /* Make something up, but don't display it now */
\r
8973 if (cm == PositionDiagram) {
\r
8976 Board initial_position;
\r
8978 if (appData.debugMode)
\r
8979 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
8981 if (!startedFromSetupPosition) {
\r
8983 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
8984 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
8994 initial_position[i][j++] = CharToPiece(*p);
\r
8997 while (*p == ' ' || *p == '\t' ||
\r
8998 *p == '\n' || *p == '\r') p++;
\r
9000 if (strncmp(p, "black", strlen("black"))==0)
\r
9001 blackPlaysFirst = TRUE;
\r
9003 blackPlaysFirst = FALSE;
\r
9004 startedFromSetupPosition = TRUE;
\r
9006 CopyBoard(boards[0], initial_position);
\r
9007 if (blackPlaysFirst) {
\r
9008 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9009 CopyBoard(boards[1], initial_position);
\r
9010 strcpy(moveList[0], "");
\r
9011 strcpy(parseList[0], "");
\r
9012 timeRemaining[0][1] = whiteTimeRemaining;
\r
9013 timeRemaining[1][1] = blackTimeRemaining;
\r
9014 if (commentList[0] != NULL) {
\r
9015 commentList[1] = commentList[0];
\r
9016 commentList[0] = NULL;
\r
9019 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9022 yyboardindex = forwardMostMove;
\r
9023 cm = (ChessMove) yylex();
\r
9026 if (first.pr == NoProc) {
\r
9027 StartChessProgram(&first);
\r
9029 InitChessProgram(&first, FALSE);
\r
9030 SendToProgram("force\n", &first);
\r
9031 if (startedFromSetupPosition) {
\r
9032 SendBoard(&first, forwardMostMove);
\r
9033 if (appData.debugMode) {
\r
9034 fprintf(debugFP, "Load Game\n");
\r
9036 DisplayBothClocks();
\r
9039 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9040 loadFlag = appData.suppressLoadMoves;
\r
9042 while (cm == Comment) {
\r
9044 if (appData.debugMode)
\r
9045 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9047 if (*p == '{' || *p == '[' || *p == '(') {
\r
9048 p[strlen(p) - 1] = NULLCHAR;
\r
9051 while (*p == '\n') p++;
\r
9052 AppendComment(currentMove, p);
\r
9053 yyboardindex = forwardMostMove;
\r
9054 cm = (ChessMove) yylex();
\r
9057 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9058 cm == WhiteWins || cm == BlackWins ||
\r
9059 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9060 DisplayMessage("", _("No moves in game"));
\r
9061 if (cmailMsgLoaded) {
\r
9062 if (appData.debugMode)
\r
9063 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9064 ClearHighlights();
\r
9067 DrawPosition(FALSE, boards[currentMove]);
\r
9068 DisplayBothClocks();
\r
9069 gameMode = EditGame;
\r
9071 gameFileFP = NULL;
\r
9076 // [HGM] PV info: routine tests if comment empty
\r
9077 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9078 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9080 if (!matchMode && appData.timeDelay != 0)
\r
9081 DrawPosition(FALSE, boards[currentMove]);
\r
9083 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9084 programStats.ok_to_send = 1;
\r
9087 /* if the first token after the PGN tags is a move
\r
9088 * and not move number 1, retrieve it from the parser
\r
9090 if (cm != MoveNumberOne)
\r
9091 LoadGameOneMove(cm);
\r
9093 /* load the remaining moves from the file */
\r
9094 while (LoadGameOneMove((ChessMove)0)) {
\r
9095 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9096 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9099 /* rewind to the start of the game */
\r
9100 currentMove = backwardMostMove;
\r
9102 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9104 if (oldGameMode == AnalyzeFile ||
\r
9105 oldGameMode == AnalyzeMode) {
\r
9106 AnalyzeFileEvent();
\r
9109 if (matchMode || appData.timeDelay == 0) {
\r
9111 gameMode = EditGame;
\r
9113 } else if (appData.timeDelay > 0) {
\r
9114 AutoPlayGameLoop();
\r
9117 if (appData.debugMode)
\r
9118 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9120 loadFlag = 0; /* [HGM] true game starts */
\r
9124 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9126 ReloadPosition(offset)
\r
9129 int positionNumber = lastLoadPositionNumber + offset;
\r
9130 if (lastLoadPositionFP == NULL) {
\r
9131 DisplayError(_("No position has been loaded yet"), 0);
\r
9134 if (positionNumber <= 0) {
\r
9135 DisplayError(_("Can't back up any further"), 0);
\r
9138 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9139 lastLoadPositionTitle);
\r
9142 /* Load the nth position from the given file */
\r
9144 LoadPositionFromFile(filename, n, title)
\r
9150 char buf[MSG_SIZ];
\r
9152 if (strcmp(filename, "-") == 0) {
\r
9153 return LoadPosition(stdin, n, "stdin");
\r
9155 f = fopen(filename, "rb");
\r
9157 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9158 DisplayError(buf, errno);
\r
9161 return LoadPosition(f, n, title);
\r
9166 /* Load the nth position from the given open file, and close it */
\r
9168 LoadPosition(f, positionNumber, title)
\r
9170 int positionNumber;
\r
9173 char *p, line[MSG_SIZ];
\r
9174 Board initial_position;
\r
9175 int i, j, fenMode, pn;
\r
9177 if (gameMode == Training )
\r
9178 SetTrainingModeOff();
\r
9180 if (gameMode != BeginningOfGame) {
\r
9181 Reset(FALSE, TRUE);
\r
9183 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9184 fclose(lastLoadPositionFP);
\r
9186 if (positionNumber == 0) positionNumber = 1;
\r
9187 lastLoadPositionFP = f;
\r
9188 lastLoadPositionNumber = positionNumber;
\r
9189 strcpy(lastLoadPositionTitle, title);
\r
9190 if (first.pr == NoProc) {
\r
9191 StartChessProgram(&first);
\r
9192 InitChessProgram(&first, FALSE);
\r
9194 pn = positionNumber;
\r
9195 if (positionNumber < 0) {
\r
9196 /* Negative position number means to seek to that byte offset */
\r
9197 if (fseek(f, -positionNumber, 0) == -1) {
\r
9198 DisplayError(_("Can't seek on position file"), 0);
\r
9203 if (fseek(f, 0, 0) == -1) {
\r
9204 if (f == lastLoadPositionFP ?
\r
9205 positionNumber == lastLoadPositionNumber + 1 :
\r
9206 positionNumber == 1) {
\r
9209 DisplayError(_("Can't seek on position file"), 0);
\r
9214 /* See if this file is FEN or old-style xboard */
\r
9215 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9216 DisplayError(_("Position not found in file"), 0);
\r
9220 switch (line[0]) {
\r
9221 case '#': case 'x':
\r
9225 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9226 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9227 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9228 case '7': case '8': case '9':
\r
9229 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9230 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9231 case 'C': case 'W': case 'c': case 'w':
\r
9236 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9237 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9241 if (fenMode || line[0] == '#') pn--;
\r
9243 /* skip positions before number pn */
\r
9244 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9245 Reset(TRUE, TRUE);
\r
9246 DisplayError(_("Position not found in file"), 0);
\r
9249 if (fenMode || line[0] == '#') pn--;
\r
9254 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9255 DisplayError(_("Bad FEN position in file"), 0);
\r
9259 (void) fgets(line, MSG_SIZ, f);
\r
9260 (void) fgets(line, MSG_SIZ, f);
\r
9262 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9263 (void) fgets(line, MSG_SIZ, f);
\r
9264 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9267 initial_position[i][j++] = CharToPiece(*p);
\r
9271 blackPlaysFirst = FALSE;
\r
9273 (void) fgets(line, MSG_SIZ, f);
\r
9274 if (strncmp(line, "black", strlen("black"))==0)
\r
9275 blackPlaysFirst = TRUE;
\r
9278 startedFromSetupPosition = TRUE;
\r
9280 SendToProgram("force\n", &first);
\r
9281 CopyBoard(boards[0], initial_position);
\r
9282 if (blackPlaysFirst) {
\r
9283 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9284 strcpy(moveList[0], "");
\r
9285 strcpy(parseList[0], "");
\r
9286 CopyBoard(boards[1], initial_position);
\r
9287 DisplayMessage("", _("Black to play"));
\r
9289 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9290 DisplayMessage("", _("White to play"));
\r
9292 /* [HGM] copy FEN attributes as well */
\r
9294 initialRulePlies = FENrulePlies;
\r
9295 epStatus[forwardMostMove] = FENepStatus;
\r
9296 for( i=0; i< nrCastlingRights; i++ )
\r
9297 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9299 SendBoard(&first, forwardMostMove);
\r
9300 if (appData.debugMode) {
\r
9302 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9303 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9304 fprintf(debugFP, "Load Position\n");
\r
9307 if (positionNumber > 1) {
\r
9308 sprintf(line, "%s %d", title, positionNumber);
\r
9309 DisplayTitle(line);
\r
9311 DisplayTitle(title);
\r
9313 gameMode = EditGame;
\r
9316 timeRemaining[0][1] = whiteTimeRemaining;
\r
9317 timeRemaining[1][1] = blackTimeRemaining;
\r
9318 DrawPosition(FALSE, boards[currentMove]);
\r
9325 CopyPlayerNameIntoFileName(dest, src)
\r
9326 char **dest, *src;
\r
9328 while (*src != NULLCHAR && *src != ',') {
\r
9329 if (*src == ' ') {
\r
9333 *(*dest)++ = *src++;
\r
9338 char *DefaultFileName(ext)
\r
9341 static char def[MSG_SIZ];
\r
9344 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9346 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9348 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9352 def[0] = NULLCHAR;
\r
9357 /* Save the current game to the given file */
\r
9359 SaveGameToFile(filename, append)
\r
9364 char buf[MSG_SIZ];
\r
9366 if (strcmp(filename, "-") == 0) {
\r
9367 return SaveGame(stdout, 0, NULL);
\r
9369 f = fopen(filename, append ? "a" : "w");
\r
9371 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9372 DisplayError(buf, errno);
\r
9375 return SaveGame(f, 0, NULL);
\r
9384 static char buf[MSG_SIZ];
\r
9387 p = strchr(str, ' ');
\r
9388 if (p == NULL) return str;
\r
9389 strncpy(buf, str, p - str);
\r
9390 buf[p - str] = NULLCHAR;
\r
9394 #define PGN_MAX_LINE 75
\r
9396 #define PGN_SIDE_WHITE 0
\r
9397 #define PGN_SIDE_BLACK 1
\r
9400 static int FindFirstMoveOutOfBook( int side )
\r
9404 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9405 int index = backwardMostMove;
\r
9406 int has_book_hit = 0;
\r
9408 if( (index % 2) != side ) {
\r
9412 while( index < forwardMostMove ) {
\r
9413 /* Check to see if engine is in book */
\r
9414 int depth = pvInfoList[index].depth;
\r
9415 int score = pvInfoList[index].score;
\r
9418 if( depth <= 2 ) {
\r
9421 else if( score == 0 && depth == 63 ) {
\r
9422 in_book = 1; /* Zappa */
\r
9424 else if( score == 2 && depth == 99 ) {
\r
9425 in_book = 1; /* Abrok */
\r
9428 has_book_hit += in_book;
\r
9444 void GetOutOfBookInfo( char * buf )
\r
9448 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9450 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9451 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9455 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9456 for( i=0; i<2; i++ ) {
\r
9460 if( i > 0 && oob[0] >= 0 ) {
\r
9461 strcat( buf, " " );
\r
9464 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9465 sprintf( buf+strlen(buf), "%s%.2f",
\r
9466 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9467 pvInfoList[idx].score / 100.0 );
\r
9473 /* Save game in PGN style and close the file */
\r
9478 int i, offset, linelen, newblock;
\r
9480 // char *movetext;
\r
9482 int movelen, numlen, blank;
\r
9483 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9485 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9487 tm = time((time_t *) NULL);
\r
9489 PrintPGNTags(f, &gameInfo);
\r
9491 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9492 char *fen = PositionToFEN(backwardMostMove, NULL);
\r
9493 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9494 fprintf(f, "\n{--------------\n");
\r
9495 PrintPosition(f, backwardMostMove);
\r
9496 fprintf(f, "--------------}\n");
\r
9500 /* [AS] Out of book annotation */
\r
9501 if( appData.saveOutOfBookInfo ) {
\r
9504 GetOutOfBookInfo( buf );
\r
9506 if( buf[0] != '\0' ) {
\r
9507 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9514 i = backwardMostMove;
\r
9518 while (i < forwardMostMove) {
\r
9519 /* Print comments preceding this move */
\r
9520 if (commentList[i] != NULL) {
\r
9521 if (linelen > 0) fprintf(f, "\n");
\r
9522 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9527 /* Format move number */
\r
9528 if ((i % 2) == 0) {
\r
9529 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9532 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9534 numtext[0] = NULLCHAR;
\r
9537 numlen = strlen(numtext);
\r
9540 /* Print move number */
\r
9541 blank = linelen > 0 && numlen > 0;
\r
9542 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9551 fprintf(f, numtext);
\r
9552 linelen += numlen;
\r
9555 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9558 blank = linelen > 0 && movelen > 0;
\r
9559 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9568 fprintf(f, parseList[i]);
\r
9569 linelen += movelen;
\r
9571 /* [AS] Add PV info if present */
\r
9572 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9573 /* [HGM] add time */
\r
9574 char buf[MSG_SIZ]; int seconds = 0;
\r
9577 if(i >= backwardMostMove) {
\r
9578 if(WhiteOnMove(i))
\r
9579 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9580 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9582 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9583 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9585 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9587 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9590 if( seconds <= 0) buf[0] = 0; else
\r
9591 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9592 seconds = (seconds + 4)/10; // round to full seconds
\r
9593 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9594 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9597 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9598 pvInfoList[i].score >= 0 ? "+" : "",
\r
9599 pvInfoList[i].score / 100.0,
\r
9600 pvInfoList[i].depth,
\r
9603 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9605 /* Print score/depth */
\r
9606 blank = linelen > 0 && movelen > 0;
\r
9607 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9616 fprintf(f, move_buffer);
\r
9617 linelen += movelen;
\r
9623 /* Start a new line */
\r
9624 if (linelen > 0) fprintf(f, "\n");
\r
9626 /* Print comments after last move */
\r
9627 if (commentList[i] != NULL) {
\r
9628 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9631 /* Print result */
\r
9632 if (gameInfo.resultDetails != NULL &&
\r
9633 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9634 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9635 PGNResult(gameInfo.result));
\r
9637 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9644 /* Save game in old style and close the file */
\r
9646 SaveGameOldStyle(f)
\r
9652 tm = time((time_t *) NULL);
\r
9654 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9655 PrintOpponents(f);
\r
9657 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9658 fprintf(f, "\n[--------------\n");
\r
9659 PrintPosition(f, backwardMostMove);
\r
9660 fprintf(f, "--------------]\n");
\r
9665 i = backwardMostMove;
\r
9666 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9668 while (i < forwardMostMove) {
\r
9669 if (commentList[i] != NULL) {
\r
9670 fprintf(f, "[%s]\n", commentList[i]);
\r
9673 if ((i % 2) == 1) {
\r
9674 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9677 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9679 if (commentList[i] != NULL) {
\r
9683 if (i >= forwardMostMove) {
\r
9687 fprintf(f, "%s\n", parseList[i]);
\r
9692 if (commentList[i] != NULL) {
\r
9693 fprintf(f, "[%s]\n", commentList[i]);
\r
9696 /* This isn't really the old style, but it's close enough */
\r
9697 if (gameInfo.resultDetails != NULL &&
\r
9698 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9699 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9700 gameInfo.resultDetails);
\r
9702 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9709 /* Save the current game to open file f and close the file */
\r
9711 SaveGame(f, dummy, dummy2)
\r
9716 if (gameMode == EditPosition) EditPositionDone();
\r
9717 if (appData.oldSaveStyle)
\r
9718 return SaveGameOldStyle(f);
\r
9720 return SaveGamePGN(f);
\r
9723 /* Save the current position to the given file */
\r
9725 SavePositionToFile(filename)
\r
9729 char buf[MSG_SIZ];
\r
9731 if (strcmp(filename, "-") == 0) {
\r
9732 return SavePosition(stdout, 0, NULL);
\r
9734 f = fopen(filename, "a");
\r
9736 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9737 DisplayError(buf, errno);
\r
9740 SavePosition(f, 0, NULL);
\r
9746 /* Save the current position to the given open file and close the file */
\r
9748 SavePosition(f, dummy, dummy2)
\r
9756 if (appData.oldSaveStyle) {
\r
9757 tm = time((time_t *) NULL);
\r
9759 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9760 PrintOpponents(f);
\r
9761 fprintf(f, "[--------------\n");
\r
9762 PrintPosition(f, currentMove);
\r
9763 fprintf(f, "--------------]\n");
\r
9765 fen = PositionToFEN(currentMove, NULL);
\r
9766 fprintf(f, "%s\n", fen);
\r
9774 ReloadCmailMsgEvent(unregister)
\r
9778 static char *inFilename = NULL;
\r
9779 static char *outFilename;
\r
9781 struct stat inbuf, outbuf;
\r
9784 /* Any registered moves are unregistered if unregister is set, */
\r
9785 /* i.e. invoked by the signal handler */
\r
9787 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9788 cmailMoveRegistered[i] = FALSE;
\r
9789 if (cmailCommentList[i] != NULL) {
\r
9790 free(cmailCommentList[i]);
\r
9791 cmailCommentList[i] = NULL;
\r
9794 nCmailMovesRegistered = 0;
\r
9797 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9798 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9800 nCmailResults = 0;
\r
9802 if (inFilename == NULL) {
\r
9803 /* Because the filenames are static they only get malloced once */
\r
9804 /* and they never get freed */
\r
9805 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9806 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9808 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9809 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9812 status = stat(outFilename, &outbuf);
\r
9814 cmailMailedMove = FALSE;
\r
9816 status = stat(inFilename, &inbuf);
\r
9817 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9820 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9821 counts the games, notes how each one terminated, etc.
\r
9823 It would be nice to remove this kludge and instead gather all
\r
9824 the information while building the game list. (And to keep it
\r
9825 in the game list nodes instead of having a bunch of fixed-size
\r
9826 parallel arrays.) Note this will require getting each game's
\r
9827 termination from the PGN tags, as the game list builder does
\r
9828 not process the game moves. --mann
\r
9830 cmailMsgLoaded = TRUE;
\r
9831 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9833 /* Load first game in the file or popup game menu */
\r
9834 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9836 #endif /* !WIN32 */
\r
9844 char string[MSG_SIZ];
\r
9846 if ( cmailMailedMove
\r
9847 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9848 return TRUE; /* Allow free viewing */
\r
9851 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9852 /* with the move registered when the conditions for registering no */
\r
9854 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9855 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9856 nCmailMovesRegistered --;
\r
9858 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9860 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9861 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9865 if (cmailOldMove == -1) {
\r
9866 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9870 if (currentMove > cmailOldMove + 1) {
\r
9871 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9875 if (currentMove < cmailOldMove) {
\r
9876 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9880 if (forwardMostMove > currentMove) {
\r
9881 /* Silently truncate extra moves */
\r
9885 if ( (currentMove == cmailOldMove + 1)
\r
9886 || ( (currentMove == cmailOldMove)
\r
9887 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9888 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9889 if (gameInfo.result != GameUnfinished) {
\r
9890 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9893 if (commentList[currentMove] != NULL) {
\r
9894 cmailCommentList[lastLoadGameNumber - 1]
\r
9895 = StrSave(commentList[currentMove]);
\r
9897 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9899 if (appData.debugMode)
\r
9900 fprintf(debugFP, "Saving %s for game %d\n",
\r
9901 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9904 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9906 f = fopen(string, "w");
\r
9907 if (appData.oldSaveStyle) {
\r
9908 SaveGameOldStyle(f); /* also closes the file */
\r
9910 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9911 f = fopen(string, "w");
\r
9912 SavePosition(f, 0, NULL); /* also closes the file */
\r
9914 fprintf(f, "{--------------\n");
\r
9915 PrintPosition(f, currentMove);
\r
9916 fprintf(f, "--------------}\n\n");
\r
9918 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9921 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9922 nCmailMovesRegistered ++;
\r
9923 } else if (nCmailGames == 1) {
\r
9924 DisplayError(_("You have not made a move yet"), 0);
\r
9935 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9936 FILE *commandOutput;
\r
9937 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9938 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9944 if (! cmailMsgLoaded) {
\r
9945 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9949 if (nCmailGames == nCmailResults) {
\r
9950 DisplayError(_("No unfinished games"), 0);
\r
9954 #if CMAIL_PROHIBIT_REMAIL
\r
9955 if (cmailMailedMove) {
\r
9956 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);
\r
9957 DisplayError(msg, 0);
\r
9962 if (! (cmailMailedMove || RegisterMove())) return;
\r
9964 if ( cmailMailedMove
\r
9965 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9966 sprintf(string, partCommandString,
\r
9967 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9968 commandOutput = popen(string, "r");
\r
9970 if (commandOutput == NULL) {
\r
9971 DisplayError(_("Failed to invoke cmail"), 0);
\r
9973 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9974 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
9976 if (nBuffers > 1) {
\r
9977 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
9978 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
9979 nBytes = MSG_SIZ - 1;
\r
9981 (void) memcpy(msg, buffer, nBytes);
\r
9983 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
9985 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
9986 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
9989 for (i = 0; i < nCmailGames; i ++) {
\r
9990 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
9995 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
9997 sprintf(buffer, "%s/%s.%s.archive",
\r
9999 appData.cmailGameName,
\r
10001 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10002 cmailMsgLoaded = FALSE;
\r
10006 DisplayInformation(msg);
\r
10007 pclose(commandOutput);
\r
10010 if ((*cmailMsg) != '\0') {
\r
10011 DisplayInformation(cmailMsg);
\r
10016 #endif /* !WIN32 */
\r
10025 int prependComma = 0;
\r
10027 char string[MSG_SIZ]; /* Space for game-list */
\r
10030 if (!cmailMsgLoaded) return "";
\r
10032 if (cmailMailedMove) {
\r
10033 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10035 /* Create a list of games left */
\r
10036 sprintf(string, "[");
\r
10037 for (i = 0; i < nCmailGames; i ++) {
\r
10038 if (! ( cmailMoveRegistered[i]
\r
10039 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10040 if (prependComma) {
\r
10041 sprintf(number, ",%d", i + 1);
\r
10043 sprintf(number, "%d", i + 1);
\r
10044 prependComma = 1;
\r
10047 strcat(string, number);
\r
10050 strcat(string, "]");
\r
10052 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10053 switch (nCmailGames) {
\r
10055 sprintf(cmailMsg,
\r
10056 _("Still need to make move for game\n"));
\r
10060 sprintf(cmailMsg,
\r
10061 _("Still need to make moves for both games\n"));
\r
10065 sprintf(cmailMsg,
\r
10066 _("Still need to make moves for all %d games\n"),
\r
10071 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10073 sprintf(cmailMsg,
\r
10074 _("Still need to make a move for game %s\n"),
\r
10079 if (nCmailResults == nCmailGames) {
\r
10080 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10082 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10087 sprintf(cmailMsg,
\r
10088 _("Still need to make moves for games %s\n"),
\r
10094 #endif /* WIN32 */
\r
10100 if (gameMode == Training)
\r
10101 SetTrainingModeOff();
\r
10103 Reset(TRUE, TRUE);
\r
10104 cmailMsgLoaded = FALSE;
\r
10105 if (appData.icsActive) {
\r
10106 SendToICS(ics_prefix);
\r
10107 SendToICS("refresh\n");
\r
10112 ExitEvent(status)
\r
10116 if (exiting > 2) {
\r
10117 /* Give up on clean exit */
\r
10120 if (exiting > 1) {
\r
10121 /* Keep trying for clean exit */
\r
10125 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10127 if (telnetISR != NULL) {
\r
10128 RemoveInputSource(telnetISR);
\r
10130 if (icsPR != NoProc) {
\r
10131 DestroyChildProcess(icsPR, TRUE);
\r
10134 /* Save game if resource set and not already saved by GameEnds() */
\r
10135 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10136 && forwardMostMove > 0) {
\r
10137 if (*appData.saveGameFile != NULLCHAR) {
\r
10138 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10139 } else if (appData.autoSaveGames) {
\r
10142 if (*appData.savePositionFile != NULLCHAR) {
\r
10143 SavePositionToFile(appData.savePositionFile);
\r
10146 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10148 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10149 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10151 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10152 /* make sure this other one finishes before killing it! */
\r
10153 if(endingGame) { int count = 0;
\r
10154 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10155 while(endingGame && count++ < 10) DoSleep(1);
\r
10156 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10159 /* Kill off chess programs */
\r
10160 if (first.pr != NoProc) {
\r
10161 ExitAnalyzeMode();
\r
10163 DoSleep( appData.delayBeforeQuit );
\r
10164 SendToProgram("quit\n", &first);
\r
10165 DoSleep( appData.delayAfterQuit );
\r
10166 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10168 if (second.pr != NoProc) {
\r
10169 DoSleep( appData.delayBeforeQuit );
\r
10170 SendToProgram("quit\n", &second);
\r
10171 DoSleep( appData.delayAfterQuit );
\r
10172 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10174 if (first.isr != NULL) {
\r
10175 RemoveInputSource(first.isr);
\r
10177 if (second.isr != NULL) {
\r
10178 RemoveInputSource(second.isr);
\r
10181 ShutDownFrontEnd();
\r
10188 if (appData.debugMode)
\r
10189 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10193 if (gameMode == MachinePlaysWhite ||
\r
10194 gameMode == MachinePlaysBlack) {
\r
10197 DisplayBothClocks();
\r
10199 if (gameMode == PlayFromGameFile) {
\r
10200 if (appData.timeDelay >= 0)
\r
10201 AutoPlayGameLoop();
\r
10202 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10203 Reset(FALSE, TRUE);
\r
10204 SendToICS(ics_prefix);
\r
10205 SendToICS("refresh\n");
\r
10206 } else if (currentMove < forwardMostMove) {
\r
10207 ForwardInner(forwardMostMove);
\r
10209 pauseExamInvalid = FALSE;
\r
10211 switch (gameMode) {
\r
10214 case IcsExamining:
\r
10215 pauseExamForwardMostMove = forwardMostMove;
\r
10216 pauseExamInvalid = FALSE;
\r
10217 /* fall through */
\r
10218 case IcsObserving:
\r
10219 case IcsPlayingWhite:
\r
10220 case IcsPlayingBlack:
\r
10224 case PlayFromGameFile:
\r
10225 (void) StopLoadGameTimer();
\r
10229 case BeginningOfGame:
\r
10230 if (appData.icsActive) return;
\r
10231 /* else fall through */
\r
10232 case MachinePlaysWhite:
\r
10233 case MachinePlaysBlack:
\r
10234 case TwoMachinesPlay:
\r
10235 if (forwardMostMove == 0)
\r
10236 return; /* don't pause if no one has moved */
\r
10237 if ((gameMode == MachinePlaysWhite &&
\r
10238 !WhiteOnMove(forwardMostMove)) ||
\r
10239 (gameMode == MachinePlaysBlack &&
\r
10240 WhiteOnMove(forwardMostMove))) {
\r
10251 EditCommentEvent()
\r
10253 char title[MSG_SIZ];
\r
10255 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10256 strcpy(title, _("Edit comment"));
\r
10258 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10259 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10260 parseList[currentMove - 1]);
\r
10263 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10270 char *tags = PGNTags(&gameInfo);
\r
10271 EditTagsPopUp(tags);
\r
10276 AnalyzeModeEvent()
\r
10278 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10281 if (gameMode != AnalyzeFile) {
\r
10282 if (!appData.icsEngineAnalyze) {
\r
10284 if (gameMode != EditGame) return;
\r
10286 ResurrectChessProgram();
\r
10287 SendToProgram("analyze\n", &first);
\r
10288 first.analyzing = TRUE;
\r
10289 /*first.maybeThinking = TRUE;*/
\r
10290 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10291 AnalysisPopUp(_("Analysis"),
\r
10292 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10294 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10299 StartAnalysisClock();
\r
10300 GetTimeMark(&lastNodeCountTime);
\r
10301 lastNodeCount = 0;
\r
10305 AnalyzeFileEvent()
\r
10307 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10310 if (gameMode != AnalyzeMode) {
\r
10312 if (gameMode != EditGame) return;
\r
10313 ResurrectChessProgram();
\r
10314 SendToProgram("analyze\n", &first);
\r
10315 first.analyzing = TRUE;
\r
10316 /*first.maybeThinking = TRUE;*/
\r
10317 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10318 AnalysisPopUp(_("Analysis"),
\r
10319 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10321 gameMode = AnalyzeFile;
\r
10326 StartAnalysisClock();
\r
10327 GetTimeMark(&lastNodeCountTime);
\r
10328 lastNodeCount = 0;
\r
10332 MachineWhiteEvent()
\r
10334 char buf[MSG_SIZ];
\r
10335 char *bookHit = NULL;
\r
10337 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10341 if (gameMode == PlayFromGameFile ||
\r
10342 gameMode == TwoMachinesPlay ||
\r
10343 gameMode == Training ||
\r
10344 gameMode == AnalyzeMode ||
\r
10345 gameMode == EndOfGame)
\r
10348 if (gameMode == EditPosition)
\r
10349 EditPositionDone();
\r
10351 if (!WhiteOnMove(currentMove)) {
\r
10352 DisplayError(_("It is not White's turn"), 0);
\r
10356 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10357 ExitAnalyzeMode();
\r
10359 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10360 gameMode == AnalyzeFile)
\r
10363 ResurrectChessProgram(); /* in case it isn't running */
\r
10364 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10365 gameMode = MachinePlaysWhite;
\r
10368 gameMode = MachinePlaysWhite;
\r
10372 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10373 DisplayTitle(buf);
\r
10374 if (first.sendName) {
\r
10375 sprintf(buf, "name %s\n", gameInfo.black);
\r
10376 SendToProgram(buf, &first);
\r
10378 if (first.sendTime) {
\r
10379 if (first.useColors) {
\r
10380 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10382 SendTimeRemaining(&first, TRUE);
\r
10384 if (first.useColors) {
\r
10385 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10387 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10388 SetMachineThinkingEnables();
\r
10389 first.maybeThinking = TRUE;
\r
10392 if (appData.autoFlipView && !flipView) {
\r
10393 flipView = !flipView;
\r
10394 DrawPosition(FALSE, NULL);
\r
10395 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10398 if(bookHit) { // [HGM] book: simulate book reply
\r
10399 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10401 programStats.nodes = programStats.depth = programStats.time =
\r
10402 programStats.score = programStats.got_only_move = 0;
\r
10403 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10405 strcpy(bookMove, "move ");
\r
10406 strcat(bookMove, bookHit);
\r
10407 HandleMachineMove(bookMove, &first);
\r
10412 MachineBlackEvent()
\r
10414 char buf[MSG_SIZ];
\r
10415 char *bookHit = NULL;
\r
10417 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10421 if (gameMode == PlayFromGameFile ||
\r
10422 gameMode == TwoMachinesPlay ||
\r
10423 gameMode == Training ||
\r
10424 gameMode == AnalyzeMode ||
\r
10425 gameMode == EndOfGame)
\r
10428 if (gameMode == EditPosition)
\r
10429 EditPositionDone();
\r
10431 if (WhiteOnMove(currentMove)) {
\r
10432 DisplayError(_("It is not Black's turn"), 0);
\r
10436 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10437 ExitAnalyzeMode();
\r
10439 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10440 gameMode == AnalyzeFile)
\r
10443 ResurrectChessProgram(); /* in case it isn't running */
\r
10444 gameMode = MachinePlaysBlack;
\r
10448 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10449 DisplayTitle(buf);
\r
10450 if (first.sendName) {
\r
10451 sprintf(buf, "name %s\n", gameInfo.white);
\r
10452 SendToProgram(buf, &first);
\r
10454 if (first.sendTime) {
\r
10455 if (first.useColors) {
\r
10456 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10458 SendTimeRemaining(&first, FALSE);
\r
10460 if (first.useColors) {
\r
10461 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10463 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10464 SetMachineThinkingEnables();
\r
10465 first.maybeThinking = TRUE;
\r
10468 if (appData.autoFlipView && flipView) {
\r
10469 flipView = !flipView;
\r
10470 DrawPosition(FALSE, NULL);
\r
10471 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10473 if(bookHit) { // [HGM] book: simulate book reply
\r
10474 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10476 programStats.nodes = programStats.depth = programStats.time =
\r
10477 programStats.score = programStats.got_only_move = 0;
\r
10478 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10480 strcpy(bookMove, "move ");
\r
10481 strcat(bookMove, bookHit);
\r
10482 HandleMachineMove(bookMove, &first);
\r
10488 DisplayTwoMachinesTitle()
\r
10490 char buf[MSG_SIZ];
\r
10491 if (appData.matchGames > 0) {
\r
10492 if (first.twoMachinesColor[0] == 'w') {
\r
10493 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10494 gameInfo.white, gameInfo.black,
\r
10495 first.matchWins, second.matchWins,
\r
10496 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10498 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10499 gameInfo.white, gameInfo.black,
\r
10500 second.matchWins, first.matchWins,
\r
10501 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10504 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10506 DisplayTitle(buf);
\r
10510 TwoMachinesEvent P((void))
\r
10513 char buf[MSG_SIZ];
\r
10514 ChessProgramState *onmove;
\r
10515 char *bookHit = NULL;
\r
10517 if (appData.noChessProgram) return;
\r
10519 switch (gameMode) {
\r
10520 case TwoMachinesPlay:
\r
10522 case MachinePlaysWhite:
\r
10523 case MachinePlaysBlack:
\r
10524 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10525 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10528 /* fall through */
\r
10529 case BeginningOfGame:
\r
10530 case PlayFromGameFile:
\r
10533 if (gameMode != EditGame) return;
\r
10535 case EditPosition:
\r
10536 EditPositionDone();
\r
10538 case AnalyzeMode:
\r
10539 case AnalyzeFile:
\r
10540 ExitAnalyzeMode();
\r
10547 forwardMostMove = currentMove;
\r
10548 ResurrectChessProgram(); /* in case first program isn't running */
\r
10550 if (second.pr == NULL) {
\r
10551 StartChessProgram(&second);
\r
10552 if (second.protocolVersion == 1) {
\r
10553 TwoMachinesEventIfReady();
\r
10555 /* kludge: allow timeout for initial "feature" command */
\r
10557 DisplayMessage("", _("Starting second chess program"));
\r
10558 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10562 DisplayMessage("", "");
\r
10563 InitChessProgram(&second, FALSE);
\r
10564 SendToProgram("force\n", &second);
\r
10565 if (startedFromSetupPosition) {
\r
10566 SendBoard(&second, backwardMostMove);
\r
10567 if (appData.debugMode) {
\r
10568 fprintf(debugFP, "Two Machines\n");
\r
10571 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10572 SendMoveToProgram(i, &second);
\r
10575 gameMode = TwoMachinesPlay;
\r
10579 DisplayTwoMachinesTitle();
\r
10580 firstMove = TRUE;
\r
10581 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10584 onmove = &second;
\r
10587 SendToProgram(first.computerString, &first);
\r
10588 if (first.sendName) {
\r
10589 sprintf(buf, "name %s\n", second.tidy);
\r
10590 SendToProgram(buf, &first);
\r
10592 SendToProgram(second.computerString, &second);
\r
10593 if (second.sendName) {
\r
10594 sprintf(buf, "name %s\n", first.tidy);
\r
10595 SendToProgram(buf, &second);
\r
10599 if (!first.sendTime || !second.sendTime) {
\r
10600 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10601 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10603 if (onmove->sendTime) {
\r
10604 if (onmove->useColors) {
\r
10605 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10607 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10609 if (onmove->useColors) {
\r
10610 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10612 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10613 // SendToProgram("go\n", onmove);
\r
10614 onmove->maybeThinking = TRUE;
\r
10615 SetMachineThinkingEnables();
\r
10619 if(bookHit) { // [HGM] book: simulate book reply
\r
10620 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10622 programStats.nodes = programStats.depth = programStats.time =
\r
10623 programStats.score = programStats.got_only_move = 0;
\r
10624 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10626 strcpy(bookMove, "move ");
\r
10627 strcat(bookMove, bookHit);
\r
10628 HandleMachineMove(bookMove, &first);
\r
10635 if (gameMode == Training) {
\r
10636 SetTrainingModeOff();
\r
10637 gameMode = PlayFromGameFile;
\r
10638 DisplayMessage("", _("Training mode off"));
\r
10640 gameMode = Training;
\r
10641 animateTraining = appData.animate;
\r
10643 /* make sure we are not already at the end of the game */
\r
10644 if (currentMove < forwardMostMove) {
\r
10645 SetTrainingModeOn();
\r
10646 DisplayMessage("", _("Training mode on"));
\r
10648 gameMode = PlayFromGameFile;
\r
10649 DisplayError(_("Already at end of game"), 0);
\r
10658 if (!appData.icsActive) return;
\r
10659 switch (gameMode) {
\r
10660 case IcsPlayingWhite:
\r
10661 case IcsPlayingBlack:
\r
10662 case IcsObserving:
\r
10664 case BeginningOfGame:
\r
10665 case IcsExamining:
\r
10671 case EditPosition:
\r
10672 EditPositionDone();
\r
10675 case AnalyzeMode:
\r
10676 case AnalyzeFile:
\r
10677 ExitAnalyzeMode();
\r
10685 gameMode = IcsIdle;
\r
10696 switch (gameMode) {
\r
10698 SetTrainingModeOff();
\r
10700 case MachinePlaysWhite:
\r
10701 case MachinePlaysBlack:
\r
10702 case BeginningOfGame:
\r
10703 SendToProgram("force\n", &first);
\r
10704 SetUserThinkingEnables();
\r
10706 case PlayFromGameFile:
\r
10707 (void) StopLoadGameTimer();
\r
10708 if (gameFileFP != NULL) {
\r
10709 gameFileFP = NULL;
\r
10712 case EditPosition:
\r
10713 EditPositionDone();
\r
10715 case AnalyzeMode:
\r
10716 case AnalyzeFile:
\r
10717 ExitAnalyzeMode();
\r
10718 SendToProgram("force\n", &first);
\r
10720 case TwoMachinesPlay:
\r
10721 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10722 ResurrectChessProgram();
\r
10723 SetUserThinkingEnables();
\r
10726 ResurrectChessProgram();
\r
10728 case IcsPlayingBlack:
\r
10729 case IcsPlayingWhite:
\r
10730 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10732 case IcsObserving:
\r
10733 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10735 case IcsExamining:
\r
10736 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10747 first.offeredDraw = second.offeredDraw = 0;
\r
10749 if (gameMode == PlayFromGameFile) {
\r
10750 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10751 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10752 DisplayTitle("");
\r
10755 if (gameMode == MachinePlaysWhite ||
\r
10756 gameMode == MachinePlaysBlack ||
\r
10757 gameMode == TwoMachinesPlay ||
\r
10758 gameMode == EndOfGame) {
\r
10759 i = forwardMostMove;
\r
10760 while (i > currentMove) {
\r
10761 SendToProgram("undo\n", &first);
\r
10764 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10765 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10766 DisplayBothClocks();
\r
10767 if (whiteFlag || blackFlag) {
\r
10768 whiteFlag = blackFlag = 0;
\r
10770 DisplayTitle("");
\r
10773 gameMode = EditGame;
\r
10780 EditPositionEvent()
\r
10782 if (gameMode == EditPosition) {
\r
10788 if (gameMode != EditGame) return;
\r
10790 gameMode = EditPosition;
\r
10793 if (currentMove > 0)
\r
10794 CopyBoard(boards[0], boards[currentMove]);
\r
10796 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10798 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10799 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10804 ExitAnalyzeMode()
\r
10806 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10807 if (appData.icsEngineAnalyze) {
\r
10808 appData.icsEngineAnalyze = FALSE;
\r
10810 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10812 if (first.analysisSupport && first.analyzing) {
\r
10813 SendToProgram("exit\n", &first);
\r
10814 first.analyzing = FALSE;
\r
10816 AnalysisPopDown();
\r
10817 thinkOutput[0] = NULLCHAR;
\r
10821 EditPositionDone()
\r
10823 startedFromSetupPosition = TRUE;
\r
10824 InitChessProgram(&first, FALSE);
\r
10825 SendToProgram("force\n", &first);
\r
10826 if (blackPlaysFirst) {
\r
10827 strcpy(moveList[0], "");
\r
10828 strcpy(parseList[0], "");
\r
10829 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10830 CopyBoard(boards[1], boards[0]);
\r
10831 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10833 epStatus[1] = epStatus[0];
\r
10834 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10837 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10839 SendBoard(&first, forwardMostMove);
\r
10840 if (appData.debugMode) {
\r
10841 fprintf(debugFP, "EditPosDone\n");
\r
10843 DisplayTitle("");
\r
10844 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10845 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10846 gameMode = EditGame;
\r
10848 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10849 ClearHighlights(); /* [AS] */
\r
10852 /* Pause for `ms' milliseconds */
\r
10853 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10860 GetTimeMark(&m1);
\r
10862 GetTimeMark(&m2);
\r
10863 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10866 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10868 SendMultiLineToICS(buf)
\r
10871 char temp[MSG_SIZ+1], *p;
\r
10874 len = strlen(buf);
\r
10875 if (len > MSG_SIZ)
\r
10878 strncpy(temp, buf, len);
\r
10883 if (*p == '\n' || *p == '\r')
\r
10888 strcat(temp, "\n");
\r
10890 SendToPlayer(temp, strlen(temp));
\r
10894 SetWhiteToPlayEvent()
\r
10896 if (gameMode == EditPosition) {
\r
10897 blackPlaysFirst = FALSE;
\r
10898 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10899 } else if (gameMode == IcsExamining) {
\r
10900 SendToICS(ics_prefix);
\r
10901 SendToICS("tomove white\n");
\r
10906 SetBlackToPlayEvent()
\r
10908 if (gameMode == EditPosition) {
\r
10909 blackPlaysFirst = TRUE;
\r
10910 currentMove = 1; /* kludge */
\r
10911 DisplayBothClocks();
\r
10913 } else if (gameMode == IcsExamining) {
\r
10914 SendToICS(ics_prefix);
\r
10915 SendToICS("tomove black\n");
\r
10920 EditPositionMenuEvent(selection, x, y)
\r
10921 ChessSquare selection;
\r
10924 char buf[MSG_SIZ];
\r
10925 ChessSquare piece = boards[0][y][x];
\r
10927 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10929 switch (selection) {
\r
10931 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10932 SendToICS(ics_prefix);
\r
10933 SendToICS("bsetup clear\n");
\r
10934 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10935 SendToICS(ics_prefix);
\r
10936 SendToICS("clearboard\n");
\r
10938 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10939 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10940 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10941 if (gameMode == IcsExamining) {
\r
10942 if (boards[currentMove][y][x] != EmptySquare) {
\r
10943 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10944 AAA + x, ONE + y);
\r
10948 boards[0][y][x] = p;
\r
10953 if (gameMode == EditPosition) {
\r
10954 DrawPosition(FALSE, boards[0]);
\r
10959 SetWhiteToPlayEvent();
\r
10963 SetBlackToPlayEvent();
\r
10966 case EmptySquare:
\r
10967 if (gameMode == IcsExamining) {
\r
10968 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10971 boards[0][y][x] = EmptySquare;
\r
10972 DrawPosition(FALSE, boards[0]);
\r
10976 case PromotePiece:
\r
10977 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
10978 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
10979 selection = (ChessSquare) (PROMOTED piece);
\r
10980 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
10981 else selection = (ChessSquare)((int)piece - 1);
\r
10982 goto defaultlabel;
\r
10984 case DemotePiece:
\r
10985 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
10986 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
10987 selection = (ChessSquare) (DEMOTED piece);
\r
10988 } else if(piece == EmptySquare) selection = BlackSilver;
\r
10989 else selection = (ChessSquare)((int)piece + 1);
\r
10990 goto defaultlabel;
\r
10994 if(gameInfo.variant == VariantShatranj ||
\r
10995 gameInfo.variant == VariantXiangqi ||
\r
10996 gameInfo.variant == VariantCourier )
\r
10997 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
10998 goto defaultlabel;
\r
11002 if(gameInfo.variant == VariantXiangqi)
\r
11003 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11004 if(gameInfo.variant == VariantKnightmate)
\r
11005 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11008 if (gameMode == IcsExamining) {
\r
11009 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11010 PieceToChar(selection), AAA + x, ONE + y);
\r
11013 boards[0][y][x] = selection;
\r
11014 DrawPosition(FALSE, boards[0]);
\r
11022 DropMenuEvent(selection, x, y)
\r
11023 ChessSquare selection;
\r
11026 ChessMove moveType;
\r
11028 switch (gameMode) {
\r
11029 case IcsPlayingWhite:
\r
11030 case MachinePlaysBlack:
\r
11031 if (!WhiteOnMove(currentMove)) {
\r
11032 DisplayMoveError(_("It is Black's turn"));
\r
11035 moveType = WhiteDrop;
\r
11037 case IcsPlayingBlack:
\r
11038 case MachinePlaysWhite:
\r
11039 if (WhiteOnMove(currentMove)) {
\r
11040 DisplayMoveError(_("It is White's turn"));
\r
11043 moveType = BlackDrop;
\r
11046 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11052 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11053 selection = (ChessSquare) ((int) selection
\r
11054 + (int) BlackPawn - (int) WhitePawn);
\r
11056 if (boards[currentMove][y][x] != EmptySquare) {
\r
11057 DisplayMoveError(_("That square is occupied"));
\r
11061 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11067 /* Accept a pending offer of any kind from opponent */
\r
11069 if (appData.icsActive) {
\r
11070 SendToICS(ics_prefix);
\r
11071 SendToICS("accept\n");
\r
11072 } else if (cmailMsgLoaded) {
\r
11073 if (currentMove == cmailOldMove &&
\r
11074 commentList[cmailOldMove] != NULL &&
\r
11075 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11076 "Black offers a draw" : "White offers a draw")) {
\r
11078 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11079 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11081 DisplayError(_("There is no pending offer on this move"), 0);
\r
11082 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11085 /* Not used for offers from chess program */
\r
11092 /* Decline a pending offer of any kind from opponent */
\r
11094 if (appData.icsActive) {
\r
11095 SendToICS(ics_prefix);
\r
11096 SendToICS("decline\n");
\r
11097 } else if (cmailMsgLoaded) {
\r
11098 if (currentMove == cmailOldMove &&
\r
11099 commentList[cmailOldMove] != NULL &&
\r
11100 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11101 "Black offers a draw" : "White offers a draw")) {
\r
11103 AppendComment(cmailOldMove, "Draw declined");
\r
11104 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11105 #endif /*NOTDEF*/
\r
11107 DisplayError(_("There is no pending offer on this move"), 0);
\r
11110 /* Not used for offers from chess program */
\r
11117 /* Issue ICS rematch command */
\r
11118 if (appData.icsActive) {
\r
11119 SendToICS(ics_prefix);
\r
11120 SendToICS("rematch\n");
\r
11127 /* Call your opponent's flag (claim a win on time) */
\r
11128 if (appData.icsActive) {
\r
11129 SendToICS(ics_prefix);
\r
11130 SendToICS("flag\n");
\r
11132 switch (gameMode) {
\r
11135 case MachinePlaysWhite:
\r
11138 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11141 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11143 DisplayError(_("Your opponent is not out of time"), 0);
\r
11146 case MachinePlaysBlack:
\r
11149 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11152 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11154 DisplayError(_("Your opponent is not out of time"), 0);
\r
11164 /* Offer draw or accept pending draw offer from opponent */
\r
11166 if (appData.icsActive) {
\r
11167 /* Note: tournament rules require draw offers to be
\r
11168 made after you make your move but before you punch
\r
11169 your clock. Currently ICS doesn't let you do that;
\r
11170 instead, you immediately punch your clock after making
\r
11171 a move, but you can offer a draw at any time. */
\r
11173 SendToICS(ics_prefix);
\r
11174 SendToICS("draw\n");
\r
11175 } else if (cmailMsgLoaded) {
\r
11176 if (currentMove == cmailOldMove &&
\r
11177 commentList[cmailOldMove] != NULL &&
\r
11178 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11179 "Black offers a draw" : "White offers a draw")) {
\r
11180 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11181 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11182 } else if (currentMove == cmailOldMove + 1) {
\r
11183 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11184 "White offers a draw" : "Black offers a draw";
\r
11185 AppendComment(currentMove, offer);
\r
11186 DisplayComment(currentMove - 1, offer);
\r
11187 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11189 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11190 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11192 } else if (first.offeredDraw) {
\r
11193 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11195 if (first.sendDrawOffers) {
\r
11196 SendToProgram("draw\n", &first);
\r
11197 userOfferedDraw = TRUE;
\r
11205 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11207 if (appData.icsActive) {
\r
11208 SendToICS(ics_prefix);
\r
11209 SendToICS("adjourn\n");
\r
11211 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11219 /* Offer Abort or accept pending Abort offer from opponent */
\r
11221 if (appData.icsActive) {
\r
11222 SendToICS(ics_prefix);
\r
11223 SendToICS("abort\n");
\r
11225 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11232 /* Resign. You can do this even if it's not your turn. */
\r
11234 if (appData.icsActive) {
\r
11235 SendToICS(ics_prefix);
\r
11236 SendToICS("resign\n");
\r
11238 switch (gameMode) {
\r
11239 case MachinePlaysWhite:
\r
11240 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11242 case MachinePlaysBlack:
\r
11243 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11246 if (cmailMsgLoaded) {
\r
11248 if (WhiteOnMove(cmailOldMove)) {
\r
11249 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11251 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11253 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11264 StopObservingEvent()
\r
11266 /* Stop observing current games */
\r
11267 SendToICS(ics_prefix);
\r
11268 SendToICS("unobserve\n");
\r
11272 StopExaminingEvent()
\r
11274 /* Stop observing current game */
\r
11275 SendToICS(ics_prefix);
\r
11276 SendToICS("unexamine\n");
\r
11280 ForwardInner(target)
\r
11285 if (appData.debugMode)
\r
11286 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11287 target, currentMove, forwardMostMove);
\r
11289 if (gameMode == EditPosition)
\r
11292 if (gameMode == PlayFromGameFile && !pausing)
\r
11295 if (gameMode == IcsExamining && pausing)
\r
11296 limit = pauseExamForwardMostMove;
\r
11298 limit = forwardMostMove;
\r
11300 if (target > limit) target = limit;
\r
11302 if (target > 0 && moveList[target - 1][0]) {
\r
11303 int fromX, fromY, toX, toY;
\r
11304 toX = moveList[target - 1][2] - AAA;
\r
11305 toY = moveList[target - 1][3] - ONE;
\r
11306 if (moveList[target - 1][1] == '@') {
\r
11307 if (appData.highlightLastMove) {
\r
11308 SetHighlights(-1, -1, toX, toY);
\r
11311 fromX = moveList[target - 1][0] - AAA;
\r
11312 fromY = moveList[target - 1][1] - ONE;
\r
11313 if (target == currentMove + 1) {
\r
11314 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11316 if (appData.highlightLastMove) {
\r
11317 SetHighlights(fromX, fromY, toX, toY);
\r
11321 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11322 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11323 gameMode == AnalyzeFile) {
\r
11324 while (currentMove < target) {
\r
11325 SendMoveToProgram(currentMove++, &first);
\r
11328 currentMove = target;
\r
11331 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11332 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11333 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11335 DisplayBothClocks();
\r
11336 DisplayMove(currentMove - 1);
\r
11337 DrawPosition(FALSE, boards[currentMove]);
\r
11338 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11339 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11340 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11348 if (gameMode == IcsExamining && !pausing) {
\r
11349 SendToICS(ics_prefix);
\r
11350 SendToICS("forward\n");
\r
11352 ForwardInner(currentMove + 1);
\r
11359 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11360 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11361 * the remaining moves to the engine. Otherwise we get analysis output
\r
11362 * after each move.
\r
11364 if (first.analysisSupport) {
\r
11365 SendToProgram("exit\nforce\n", &first);
\r
11366 first.analyzing = FALSE;
\r
11370 if (gameMode == IcsExamining && !pausing) {
\r
11371 SendToICS(ics_prefix);
\r
11372 SendToICS("forward 999999\n");
\r
11374 ForwardInner(forwardMostMove);
\r
11377 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11378 /* we have fed all the moves, so reactivate analysis mode */
\r
11379 SendToProgram("analyze\n", &first);
\r
11380 first.analyzing = TRUE;
\r
11381 /*first.maybeThinking = TRUE;*/
\r
11382 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11387 BackwardInner(target)
\r
11390 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11392 if (appData.debugMode)
\r
11393 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11394 target, currentMove, forwardMostMove);
\r
11396 if (gameMode == EditPosition) return;
\r
11397 if (currentMove <= backwardMostMove) {
\r
11398 ClearHighlights();
\r
11399 DrawPosition(full_redraw, boards[currentMove]);
\r
11402 if (gameMode == PlayFromGameFile && !pausing)
\r
11405 if (moveList[target][0]) {
\r
11406 int fromX, fromY, toX, toY;
\r
11407 toX = moveList[target][2] - AAA;
\r
11408 toY = moveList[target][3] - ONE;
\r
11409 if (moveList[target][1] == '@') {
\r
11410 if (appData.highlightLastMove) {
\r
11411 SetHighlights(-1, -1, toX, toY);
\r
11414 fromX = moveList[target][0] - AAA;
\r
11415 fromY = moveList[target][1] - ONE;
\r
11416 if (target == currentMove - 1) {
\r
11417 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11419 if (appData.highlightLastMove) {
\r
11420 SetHighlights(fromX, fromY, toX, toY);
\r
11424 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11425 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11426 while (currentMove > target) {
\r
11427 SendToProgram("undo\n", &first);
\r
11431 currentMove = target;
\r
11434 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11435 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11436 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11438 DisplayBothClocks();
\r
11439 DisplayMove(currentMove - 1);
\r
11440 DrawPosition(full_redraw, boards[currentMove]);
\r
11441 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11442 // [HGM] PV info: routine tests if comment empty
\r
11443 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11449 if (gameMode == IcsExamining && !pausing) {
\r
11450 SendToICS(ics_prefix);
\r
11451 SendToICS("backward\n");
\r
11453 BackwardInner(currentMove - 1);
\r
11460 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11461 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11462 * all the moves. Otherwise we get analysis output after each undo.
\r
11464 if (first.analysisSupport) {
\r
11465 SendToProgram("exit\nforce\n", &first);
\r
11466 first.analyzing = FALSE;
\r
11470 if (gameMode == IcsExamining && !pausing) {
\r
11471 SendToICS(ics_prefix);
\r
11472 SendToICS("backward 999999\n");
\r
11474 BackwardInner(backwardMostMove);
\r
11477 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11478 /* we have fed all the moves, so reactivate analysis mode */
\r
11479 SendToProgram("analyze\n", &first);
\r
11480 first.analyzing = TRUE;
\r
11481 /*first.maybeThinking = TRUE;*/
\r
11482 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11487 ToNrEvent(int to)
\r
11489 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11490 if (to >= forwardMostMove) to = forwardMostMove;
\r
11491 if (to <= backwardMostMove) to = backwardMostMove;
\r
11492 if (to < currentMove) {
\r
11493 BackwardInner(to);
\r
11495 ForwardInner(to);
\r
11502 if (gameMode != IcsExamining) {
\r
11503 DisplayError(_("You are not examining a game"), 0);
\r
11507 DisplayError(_("You can't revert while pausing"), 0);
\r
11510 SendToICS(ics_prefix);
\r
11511 SendToICS("revert\n");
\r
11515 RetractMoveEvent()
\r
11517 switch (gameMode) {
\r
11518 case MachinePlaysWhite:
\r
11519 case MachinePlaysBlack:
\r
11520 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11521 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11524 if (forwardMostMove < 2) return;
\r
11525 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11526 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11527 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11528 DisplayBothClocks();
\r
11529 DisplayMove(currentMove - 1);
\r
11530 ClearHighlights();/*!! could figure this out*/
\r
11531 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11532 SendToProgram("remove\n", &first);
\r
11533 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11536 case BeginningOfGame:
\r
11540 case IcsPlayingWhite:
\r
11541 case IcsPlayingBlack:
\r
11542 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11543 SendToICS(ics_prefix);
\r
11544 SendToICS("takeback 2\n");
\r
11546 SendToICS(ics_prefix);
\r
11547 SendToICS("takeback 1\n");
\r
11556 ChessProgramState *cps;
\r
11558 switch (gameMode) {
\r
11559 case MachinePlaysWhite:
\r
11560 if (!WhiteOnMove(forwardMostMove)) {
\r
11561 DisplayError(_("It is your turn"), 0);
\r
11566 case MachinePlaysBlack:
\r
11567 if (WhiteOnMove(forwardMostMove)) {
\r
11568 DisplayError(_("It is your turn"), 0);
\r
11573 case TwoMachinesPlay:
\r
11574 if (WhiteOnMove(forwardMostMove) ==
\r
11575 (first.twoMachinesColor[0] == 'w')) {
\r
11581 case BeginningOfGame:
\r
11585 SendToProgram("?\n", cps);
\r
11589 TruncateGameEvent()
\r
11592 if (gameMode != EditGame) return;
\r
11599 if (forwardMostMove > currentMove) {
\r
11600 if (gameInfo.resultDetails != NULL) {
\r
11601 free(gameInfo.resultDetails);
\r
11602 gameInfo.resultDetails = NULL;
\r
11603 gameInfo.result = GameUnfinished;
\r
11605 forwardMostMove = currentMove;
\r
11606 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11614 if (appData.noChessProgram) return;
\r
11615 switch (gameMode) {
\r
11616 case MachinePlaysWhite:
\r
11617 if (WhiteOnMove(forwardMostMove)) {
\r
11618 DisplayError(_("Wait until your turn"), 0);
\r
11622 case BeginningOfGame:
\r
11623 case MachinePlaysBlack:
\r
11624 if (!WhiteOnMove(forwardMostMove)) {
\r
11625 DisplayError(_("Wait until your turn"), 0);
\r
11630 DisplayError(_("No hint available"), 0);
\r
11633 SendToProgram("hint\n", &first);
\r
11634 hintRequested = TRUE;
\r
11640 if (appData.noChessProgram) return;
\r
11641 switch (gameMode) {
\r
11642 case MachinePlaysWhite:
\r
11643 if (WhiteOnMove(forwardMostMove)) {
\r
11644 DisplayError(_("Wait until your turn"), 0);
\r
11648 case BeginningOfGame:
\r
11649 case MachinePlaysBlack:
\r
11650 if (!WhiteOnMove(forwardMostMove)) {
\r
11651 DisplayError(_("Wait until your turn"), 0);
\r
11655 case EditPosition:
\r
11656 EditPositionDone();
\r
11658 case TwoMachinesPlay:
\r
11663 SendToProgram("bk\n", &first);
\r
11664 bookOutput[0] = NULLCHAR;
\r
11665 bookRequested = TRUE;
\r
11671 char *tags = PGNTags(&gameInfo);
\r
11672 TagsPopUp(tags, CmailMsg());
\r
11676 /* end button procedures */
\r
11679 PrintPosition(fp, move)
\r
11685 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11686 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11687 char c = PieceToChar(boards[move][i][j]);
\r
11688 fputc(c == 'x' ? '.' : c, fp);
\r
11689 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11692 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11693 fprintf(fp, "white to play\n");
\r
11695 fprintf(fp, "black to play\n");
\r
11699 PrintOpponents(fp)
\r
11702 if (gameInfo.white != NULL) {
\r
11703 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11705 fprintf(fp, "\n");
\r
11709 /* Find last component of program's own name, using some heuristics */
\r
11711 TidyProgramName(prog, host, buf)
\r
11712 char *prog, *host, buf[MSG_SIZ];
\r
11715 int local = (strcmp(host, "localhost") == 0);
\r
11716 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11718 while (*p == ' ') p++;
\r
11721 if (*prog == '"' || *prog == '\'') {
\r
11722 q = strchr(prog + 1, *prog);
\r
11724 q = strchr(prog, ' ');
\r
11726 if (q == NULL) q = prog + strlen(prog);
\r
11728 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11730 if(p == prog && *p == '"') p++;
\r
11731 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11732 memcpy(buf, p, q - p);
\r
11733 buf[q - p] = NULLCHAR;
\r
11735 strcat(buf, "@");
\r
11736 strcat(buf, host);
\r
11741 TimeControlTagValue()
\r
11743 char buf[MSG_SIZ];
\r
11744 if (!appData.clockMode) {
\r
11745 strcpy(buf, "-");
\r
11746 } else if (movesPerSession > 0) {
\r
11747 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11748 } else if (timeIncrement == 0) {
\r
11749 sprintf(buf, "%ld", timeControl/1000);
\r
11751 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11753 return StrSave(buf);
\r
11759 /* This routine is used only for certain modes */
\r
11760 VariantClass v = gameInfo.variant;
\r
11761 ClearGameInfo(&gameInfo);
\r
11762 gameInfo.variant = v;
\r
11764 switch (gameMode) {
\r
11765 case MachinePlaysWhite:
\r
11766 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11767 gameInfo.site = StrSave(HostName());
\r
11768 gameInfo.date = PGNDate();
\r
11769 gameInfo.round = StrSave("-");
\r
11770 gameInfo.white = StrSave(first.tidy);
\r
11771 gameInfo.black = StrSave(UserName());
\r
11772 gameInfo.timeControl = TimeControlTagValue();
\r
11775 case MachinePlaysBlack:
\r
11776 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11777 gameInfo.site = StrSave(HostName());
\r
11778 gameInfo.date = PGNDate();
\r
11779 gameInfo.round = StrSave("-");
\r
11780 gameInfo.white = StrSave(UserName());
\r
11781 gameInfo.black = StrSave(first.tidy);
\r
11782 gameInfo.timeControl = TimeControlTagValue();
\r
11785 case TwoMachinesPlay:
\r
11786 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11787 gameInfo.site = StrSave(HostName());
\r
11788 gameInfo.date = PGNDate();
\r
11789 if (matchGame > 0) {
\r
11790 char buf[MSG_SIZ];
\r
11791 sprintf(buf, "%d", matchGame);
\r
11792 gameInfo.round = StrSave(buf);
\r
11794 gameInfo.round = StrSave("-");
\r
11796 if (first.twoMachinesColor[0] == 'w') {
\r
11797 gameInfo.white = StrSave(first.tidy);
\r
11798 gameInfo.black = StrSave(second.tidy);
\r
11800 gameInfo.white = StrSave(second.tidy);
\r
11801 gameInfo.black = StrSave(first.tidy);
\r
11803 gameInfo.timeControl = TimeControlTagValue();
\r
11807 gameInfo.event = StrSave("Edited game");
\r
11808 gameInfo.site = StrSave(HostName());
\r
11809 gameInfo.date = PGNDate();
\r
11810 gameInfo.round = StrSave("-");
\r
11811 gameInfo.white = StrSave("-");
\r
11812 gameInfo.black = StrSave("-");
\r
11815 case EditPosition:
\r
11816 gameInfo.event = StrSave("Edited position");
\r
11817 gameInfo.site = StrSave(HostName());
\r
11818 gameInfo.date = PGNDate();
\r
11819 gameInfo.round = StrSave("-");
\r
11820 gameInfo.white = StrSave("-");
\r
11821 gameInfo.black = StrSave("-");
\r
11824 case IcsPlayingWhite:
\r
11825 case IcsPlayingBlack:
\r
11826 case IcsObserving:
\r
11827 case IcsExamining:
\r
11830 case PlayFromGameFile:
\r
11831 gameInfo.event = StrSave("Game from non-PGN file");
\r
11832 gameInfo.site = StrSave(HostName());
\r
11833 gameInfo.date = PGNDate();
\r
11834 gameInfo.round = StrSave("-");
\r
11835 gameInfo.white = StrSave("?");
\r
11836 gameInfo.black = StrSave("?");
\r
11845 ReplaceComment(index, text)
\r
11851 while (*text == '\n') text++;
\r
11852 len = strlen(text);
\r
11853 while (len > 0 && text[len - 1] == '\n') len--;
\r
11855 if (commentList[index] != NULL)
\r
11856 free(commentList[index]);
\r
11859 commentList[index] = NULL;
\r
11862 commentList[index] = (char *) malloc(len + 2);
\r
11863 strncpy(commentList[index], text, len);
\r
11864 commentList[index][len] = '\n';
\r
11865 commentList[index][len + 1] = NULLCHAR;
\r
11878 if (ch == '\r') continue;
\r
11880 } while (ch != '\0');
\r
11884 AppendComment(index, text)
\r
11891 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11894 while (*text == '\n') text++;
\r
11895 len = strlen(text);
\r
11896 while (len > 0 && text[len - 1] == '\n') len--;
\r
11898 if (len == 0) return;
\r
11900 if (commentList[index] != NULL) {
\r
11901 old = commentList[index];
\r
11902 oldlen = strlen(old);
\r
11903 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11904 strcpy(commentList[index], old);
\r
11906 strncpy(&commentList[index][oldlen], text, len);
\r
11907 commentList[index][oldlen + len] = '\n';
\r
11908 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11910 commentList[index] = (char *) malloc(len + 2);
\r
11911 strncpy(commentList[index], text, len);
\r
11912 commentList[index][len] = '\n';
\r
11913 commentList[index][len + 1] = NULLCHAR;
\r
11917 static char * FindStr( char * text, char * sub_text )
\r
11919 char * result = strstr( text, sub_text );
\r
11921 if( result != NULL ) {
\r
11922 result += strlen( sub_text );
\r
11928 /* [AS] Try to extract PV info from PGN comment */
\r
11929 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11930 char *GetInfoFromComment( int index, char * text )
\r
11932 char * sep = text;
\r
11934 if( text != NULL && index > 0 ) {
\r
11937 int time = -1, sec = 0, deci;
\r
11938 char * s_eval = FindStr( text, "[%eval " );
\r
11939 char * s_emt = FindStr( text, "[%emt " );
\r
11941 if( s_eval != NULL || s_emt != NULL ) {
\r
11945 if( s_eval != NULL ) {
\r
11946 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11950 if( delim != ']' ) {
\r
11955 if( s_emt != NULL ) {
\r
11959 /* We expect something like: [+|-]nnn.nn/dd */
\r
11960 int score_lo = 0;
\r
11962 sep = strchr( text, '/' );
\r
11963 if( sep == NULL || sep < (text+4) ) {
\r
11967 time = -1; sec = -1; deci = -1;
\r
11968 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11969 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
11970 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11971 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
11975 if( score_lo < 0 || score_lo >= 100 ) {
\r
11979 if(sec >= 0) time = 600*time + 10*sec; else
\r
11980 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
11982 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
11984 /* [HGM] PV time: now locate end of PV info */
\r
11985 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
11987 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
11989 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
11991 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
11992 while(*sep == ' ') sep++;
\r
11995 if( depth <= 0 ) {
\r
12003 pvInfoList[index-1].depth = depth;
\r
12004 pvInfoList[index-1].score = score;
\r
12005 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12011 SendToProgram(message, cps)
\r
12013 ChessProgramState *cps;
\r
12015 int count, outCount, error;
\r
12016 char buf[MSG_SIZ];
\r
12018 if (cps->pr == NULL) return;
\r
12021 if (appData.debugMode) {
\r
12023 GetTimeMark(&now);
\r
12024 fprintf(debugFP, "%ld >%-6s: %s",
\r
12025 SubtractTimeMarks(&now, &programStartTime),
\r
12026 cps->which, message);
\r
12029 count = strlen(message);
\r
12030 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12031 if (outCount < count && !exiting
\r
12032 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12033 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12034 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12035 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12036 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12037 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12039 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12041 gameInfo.resultDetails = buf;
\r
12043 DisplayFatalError(buf, error, 1);
\r
12048 ReceiveFromProgram(isr, closure, message, count, error)
\r
12049 InputSourceRef isr;
\r
12050 VOIDSTAR closure;
\r
12056 char buf[MSG_SIZ];
\r
12057 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12059 if (isr != cps->isr) return; /* Killed intentionally */
\r
12060 if (count <= 0) {
\r
12061 if (count == 0) {
\r
12063 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12064 cps->which, cps->program);
\r
12065 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12066 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12067 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12068 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12070 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12072 gameInfo.resultDetails = buf;
\r
12074 RemoveInputSource(cps->isr);
\r
12075 DisplayFatalError(buf, 0, 1);
\r
12078 _("Error reading from %s chess program (%s)"),
\r
12079 cps->which, cps->program);
\r
12080 RemoveInputSource(cps->isr);
\r
12082 /* [AS] Program is misbehaving badly... kill it */
\r
12083 if( count == -2 ) {
\r
12084 DestroyChildProcess( cps->pr, 9 );
\r
12085 cps->pr = NoProc;
\r
12088 DisplayFatalError(buf, error, 1);
\r
12093 if ((end_str = strchr(message, '\r')) != NULL)
\r
12094 *end_str = NULLCHAR;
\r
12095 if ((end_str = strchr(message, '\n')) != NULL)
\r
12096 *end_str = NULLCHAR;
\r
12098 if (appData.debugMode) {
\r
12099 TimeMark now; int print = 1;
\r
12100 char *quote = ""; char c; int i;
\r
12102 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12103 char start = message[0];
\r
12104 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12105 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12106 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12107 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12108 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12109 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12110 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12111 { quote = "# "; print = (appData.engineComments == 2); }
\r
12112 message[0] = start; // restore original message
\r
12115 GetTimeMark(&now);
\r
12116 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12117 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12123 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12124 if (appData.icsEngineAnalyze) {
\r
12125 if (strstr(message, "whisper") != NULL ||
\r
12126 strstr(message, "kibitz") != NULL ||
\r
12127 strstr(message, "tellics") != NULL) return;
\r
12130 HandleMachineMove(message, cps);
\r
12135 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12136 ChessProgramState *cps;
\r
12137 int mps, inc, sd, st;
\r
12140 char buf[MSG_SIZ];
\r
12143 if( timeControl_2 > 0 ) {
\r
12144 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12145 tc = timeControl_2;
\r
12148 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12149 inc /= cps->timeOdds;
\r
12150 st /= cps->timeOdds;
\r
12152 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12155 /* Set exact time per move, normally using st command */
\r
12156 if (cps->stKludge) {
\r
12157 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12158 seconds = st % 60;
\r
12159 if (seconds == 0) {
\r
12160 sprintf(buf, "level 1 %d\n", st/60);
\r
12162 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12165 sprintf(buf, "st %d\n", st);
\r
12168 /* Set conventional or incremental time control, using level command */
\r
12169 if (seconds == 0) {
\r
12170 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12171 Fixed in later versions, but still avoid :seconds
\r
12172 when seconds is 0. */
\r
12173 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12175 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12176 seconds, inc/1000);
\r
12179 SendToProgram(buf, cps);
\r
12181 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12182 /* Orthogonally, limit search to given depth */
\r
12184 if (cps->sdKludge) {
\r
12185 sprintf(buf, "depth\n%d\n", sd);
\r
12187 sprintf(buf, "sd %d\n", sd);
\r
12189 SendToProgram(buf, cps);
\r
12192 if(cps->nps > 0) { /* [HGM] nps */
\r
12193 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12195 sprintf(buf, "nps %d\n", cps->nps);
\r
12196 SendToProgram(buf, cps);
\r
12201 ChessProgramState *WhitePlayer()
\r
12202 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12204 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12205 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12211 SendTimeRemaining(cps, machineWhite)
\r
12212 ChessProgramState *cps;
\r
12213 int /*boolean*/ machineWhite;
\r
12215 char message[MSG_SIZ];
\r
12216 long time, otime;
\r
12218 /* Note: this routine must be called when the clocks are stopped
\r
12219 or when they have *just* been set or switched; otherwise
\r
12220 it will be off by the time since the current tick started.
\r
12222 if (machineWhite) {
\r
12223 time = whiteTimeRemaining / 10;
\r
12224 otime = blackTimeRemaining / 10;
\r
12226 time = blackTimeRemaining / 10;
\r
12227 otime = whiteTimeRemaining / 10;
\r
12229 /* [HGM] translate opponent's time by time-odds factor */
\r
12230 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12231 if (appData.debugMode) {
\r
12232 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12235 if (time <= 0) time = 1;
\r
12236 if (otime <= 0) otime = 1;
\r
12238 sprintf(message, "time %ld\n", time);
\r
12239 SendToProgram(message, cps);
\r
12241 sprintf(message, "otim %ld\n", otime);
\r
12242 SendToProgram(message, cps);
\r
12246 BoolFeature(p, name, loc, cps)
\r
12250 ChessProgramState *cps;
\r
12252 char buf[MSG_SIZ];
\r
12253 int len = strlen(name);
\r
12255 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12257 sscanf(*p, "%d", &val);
\r
12258 *loc = (val != 0);
\r
12259 while (**p && **p != ' ') (*p)++;
\r
12260 sprintf(buf, "accepted %s\n", name);
\r
12261 SendToProgram(buf, cps);
\r
12268 IntFeature(p, name, loc, cps)
\r
12272 ChessProgramState *cps;
\r
12274 char buf[MSG_SIZ];
\r
12275 int len = strlen(name);
\r
12276 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12278 sscanf(*p, "%d", loc);
\r
12279 while (**p && **p != ' ') (*p)++;
\r
12280 sprintf(buf, "accepted %s\n", name);
\r
12281 SendToProgram(buf, cps);
\r
12288 StringFeature(p, name, loc, cps)
\r
12292 ChessProgramState *cps;
\r
12294 char buf[MSG_SIZ];
\r
12295 int len = strlen(name);
\r
12296 if (strncmp((*p), name, len) == 0
\r
12297 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12299 sscanf(*p, "%[^\"]", loc);
\r
12300 while (**p && **p != '\"') (*p)++;
\r
12301 if (**p == '\"') (*p)++;
\r
12302 sprintf(buf, "accepted %s\n", name);
\r
12303 SendToProgram(buf, cps);
\r
12310 ParseOption(Option *opt, ChessProgramState *cps)
\r
12311 // [HGM] options: process the string that defines an engine option, and determine
\r
12312 // name, type, default value, and allowed value range
\r
12314 char *p, *q, buf[MSG_SIZ];
\r
12315 int n, min = (-1)<<31, max = 1<<31, def;
\r
12317 if(p = strstr(opt->name, " -spin ")) {
\r
12318 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12319 if(max < min) max = min; // enforce consistency
\r
12320 if(def < min) def = min;
\r
12321 if(def > max) def = max;
\r
12322 opt->value = def;
\r
12325 opt->type = Spin;
\r
12326 } else if(p = strstr(opt->name, " -string ")) {
\r
12327 opt->textValue = p+9;
\r
12328 opt->type = TextBox;
\r
12329 } else if(p = strstr(opt->name, " -check ")) {
\r
12330 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12331 opt->value = (def != 0);
\r
12332 opt->type = CheckBox;
\r
12333 } else if(p = strstr(opt->name, " -combo ")) {
\r
12334 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12335 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12336 opt->value = n = 0;
\r
12337 while(q = StrStr(q, " /// ")) {
\r
12338 n++; *q = 0; // count choices, and null-terminate each of them
\r
12340 if(*q == '*') { // remember default, which is marked with * prefix
\r
12344 cps->comboList[cps->comboCnt++] = q;
\r
12346 cps->comboList[cps->comboCnt++] = NULL;
\r
12347 opt->max = n + 1;
\r
12348 opt->type = ComboBox;
\r
12349 } else if(p = strstr(opt->name, " -button")) {
\r
12350 opt->type = Button;
\r
12351 } else if(p = strstr(opt->name, " -save")) {
\r
12352 opt->type = SaveButton;
\r
12353 } else return FALSE;
\r
12354 *p = 0; // terminate option name
\r
12355 // now look if the command-line options define a setting for this engine option.
\r
12356 if(cps->optionSettings && cps->optionSettings[0])
\r
12357 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12358 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12359 sprintf(buf, "option %s", p);
\r
12360 if(p = strstr(buf, ",")) *p = 0;
\r
12361 strcat(buf, "\n");
\r
12362 SendToProgram(buf, cps);
\r
12368 FeatureDone(cps, val)
\r
12369 ChessProgramState* cps;
\r
12372 DelayedEventCallback cb = GetDelayedEvent();
\r
12373 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12374 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12375 CancelDelayedEvent();
\r
12376 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12378 cps->initDone = val;
\r
12381 /* Parse feature command from engine */
\r
12383 ParseFeatures(args, cps)
\r
12385 ChessProgramState *cps;
\r
12390 char buf[MSG_SIZ];
\r
12393 while (*p == ' ') p++;
\r
12394 if (*p == NULLCHAR) return;
\r
12396 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12397 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12398 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12399 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12400 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12401 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12402 /* Engine can disable reuse, but can't enable it if user said no */
\r
12403 if (!val) cps->reuse = FALSE;
\r
12406 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12407 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12408 if (gameMode == TwoMachinesPlay) {
\r
12409 DisplayTwoMachinesTitle();
\r
12411 DisplayTitle("");
\r
12415 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12416 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12417 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12418 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12419 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12420 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12421 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12422 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12423 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12424 if (IntFeature(&p, "done", &val, cps)) {
\r
12425 FeatureDone(cps, val);
\r
12428 /* Added by Tord: */
\r
12429 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12430 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12431 /* End of additions by Tord */
\r
12433 /* [HGM] added features: */
\r
12434 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12435 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12436 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12437 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12438 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12439 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12440 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12441 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12442 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12443 cps->nrOptions--;
\r
12444 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12445 DisplayError(buf, 0);
\r
12449 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12450 /* End of additions by HGM */
\r
12452 /* unknown feature: complain and skip */
\r
12454 while (*q && *q != '=') q++;
\r
12455 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12456 SendToProgram(buf, cps);
\r
12460 if (*p == '\"') {
\r
12462 while (*p && *p != '\"') p++;
\r
12463 if (*p == '\"') p++;
\r
12465 while (*p && *p != ' ') p++;
\r
12473 PeriodicUpdatesEvent(newState)
\r
12476 if (newState == appData.periodicUpdates)
\r
12479 appData.periodicUpdates=newState;
\r
12481 /* Display type changes, so update it now */
\r
12482 DisplayAnalysis();
\r
12484 /* Get the ball rolling again... */
\r
12486 AnalysisPeriodicEvent(1);
\r
12487 StartAnalysisClock();
\r
12492 PonderNextMoveEvent(newState)
\r
12495 if (newState == appData.ponderNextMove) return;
\r
12496 if (gameMode == EditPosition) EditPositionDone();
\r
12498 SendToProgram("hard\n", &first);
\r
12499 if (gameMode == TwoMachinesPlay) {
\r
12500 SendToProgram("hard\n", &second);
\r
12503 SendToProgram("easy\n", &first);
\r
12504 thinkOutput[0] = NULLCHAR;
\r
12505 if (gameMode == TwoMachinesPlay) {
\r
12506 SendToProgram("easy\n", &second);
\r
12509 appData.ponderNextMove = newState;
\r
12513 NewSettingEvent(option, command, value)
\r
12515 int option, value;
\r
12517 char buf[MSG_SIZ];
\r
12519 if (gameMode == EditPosition) EditPositionDone();
\r
12520 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12521 SendToProgram(buf, &first);
\r
12522 if (gameMode == TwoMachinesPlay) {
\r
12523 SendToProgram(buf, &second);
\r
12528 ShowThinkingEvent()
\r
12529 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12531 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12532 int newState = appData.showThinking
\r
12533 // [HGM] thinking: other features now need thinking output as well
\r
12534 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12536 if (oldState == newState) return;
\r
12537 oldState = newState;
\r
12538 if (gameMode == EditPosition) EditPositionDone();
\r
12540 SendToProgram("post\n", &first);
\r
12541 if (gameMode == TwoMachinesPlay) {
\r
12542 SendToProgram("post\n", &second);
\r
12545 SendToProgram("nopost\n", &first);
\r
12546 thinkOutput[0] = NULLCHAR;
\r
12547 if (gameMode == TwoMachinesPlay) {
\r
12548 SendToProgram("nopost\n", &second);
\r
12551 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12555 AskQuestionEvent(title, question, replyPrefix, which)
\r
12556 char *title; char *question; char *replyPrefix; char *which;
\r
12558 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12559 if (pr == NoProc) return;
\r
12560 AskQuestion(title, question, replyPrefix, pr);
\r
12564 DisplayMove(moveNumber)
\r
12567 char message[MSG_SIZ];
\r
12568 char res[MSG_SIZ];
\r
12569 char cpThinkOutput[MSG_SIZ];
\r
12571 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12573 if (moveNumber == forwardMostMove - 1 ||
\r
12574 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12576 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12578 if (strchr(cpThinkOutput, '\n')) {
\r
12579 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12582 *cpThinkOutput = NULLCHAR;
\r
12585 /* [AS] Hide thinking from human user */
\r
12586 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12587 *cpThinkOutput = NULLCHAR;
\r
12588 if( thinkOutput[0] != NULLCHAR ) {
\r
12591 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12592 cpThinkOutput[i] = '.';
\r
12594 cpThinkOutput[i] = NULLCHAR;
\r
12595 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12599 if (moveNumber == forwardMostMove - 1 &&
\r
12600 gameInfo.resultDetails != NULL) {
\r
12601 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12602 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12604 sprintf(res, " {%s} %s",
\r
12605 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12608 res[0] = NULLCHAR;
\r
12611 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12612 DisplayMessage(res, cpThinkOutput);
\r
12614 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12615 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12616 parseList[moveNumber], res);
\r
12617 DisplayMessage(message, cpThinkOutput);
\r
12622 DisplayAnalysisText(text)
\r
12625 char buf[MSG_SIZ];
\r
12627 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12628 || appData.icsEngineAnalyze) {
\r
12629 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12630 AnalysisPopUp(buf, text);
\r
12635 only_one_move(str)
\r
12638 while (*str && isspace(*str)) ++str;
\r
12639 while (*str && !isspace(*str)) ++str;
\r
12640 if (!*str) return 1;
\r
12641 while (*str && isspace(*str)) ++str;
\r
12642 if (!*str) return 1;
\r
12647 DisplayAnalysis()
\r
12649 char buf[MSG_SIZ];
\r
12650 char lst[MSG_SIZ / 2];
\r
12652 static char *xtra[] = { "", " (--)", " (++)" };
\r
12655 if (programStats.time == 0) {
\r
12656 programStats.time = 1;
\r
12659 if (programStats.got_only_move) {
\r
12660 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12662 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12664 nps = (u64ToDouble(programStats.nodes) /
\r
12665 ((double)programStats.time /100.0));
\r
12667 cs = programStats.time % 100;
\r
12668 s = programStats.time / 100;
\r
12669 h = (s / (60*60));
\r
12674 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12675 if (programStats.move_name[0] != NULLCHAR) {
\r
12676 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12677 programStats.depth,
\r
12678 programStats.nr_moves-programStats.moves_left,
\r
12679 programStats.nr_moves, programStats.move_name,
\r
12680 ((float)programStats.score)/100.0, lst,
\r
12681 only_one_move(lst)?
\r
12682 xtra[programStats.got_fail] : "",
\r
12683 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12685 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12686 programStats.depth,
\r
12687 programStats.nr_moves-programStats.moves_left,
\r
12688 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12690 only_one_move(lst)?
\r
12691 xtra[programStats.got_fail] : "",
\r
12692 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12695 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12696 programStats.depth,
\r
12697 ((float)programStats.score)/100.0,
\r
12699 only_one_move(lst)?
\r
12700 xtra[programStats.got_fail] : "",
\r
12701 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12704 DisplayAnalysisText(buf);
\r
12708 DisplayComment(moveNumber, text)
\r
12712 char title[MSG_SIZ];
\r
12713 char buf[8000]; // comment can be long!
\r
12714 int score, depth;
\r
12716 if( appData.autoDisplayComment ) {
\r
12717 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12718 strcpy(title, "Comment");
\r
12720 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12721 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12722 parseList[moveNumber]);
\r
12724 } else title[0] = 0;
\r
12726 // [HGM] PV info: display PV info together with (or as) comment
\r
12727 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12728 if(text == NULL) text = "";
\r
12729 score = pvInfoList[moveNumber].score;
\r
12730 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12731 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12732 CommentPopUp(title, buf);
\r
12734 if (text != NULL)
\r
12735 CommentPopUp(title, text);
\r
12738 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12739 * might be busy thinking or pondering. It can be omitted if your
\r
12740 * gnuchess is configured to stop thinking immediately on any user
\r
12741 * input. However, that gnuchess feature depends on the FIONREAD
\r
12742 * ioctl, which does not work properly on some flavors of Unix.
\r
12746 ChessProgramState *cps;
\r
12749 if (!cps->useSigint) return;
\r
12750 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12751 switch (gameMode) {
\r
12752 case MachinePlaysWhite:
\r
12753 case MachinePlaysBlack:
\r
12754 case TwoMachinesPlay:
\r
12755 case IcsPlayingWhite:
\r
12756 case IcsPlayingBlack:
\r
12757 case AnalyzeMode:
\r
12758 case AnalyzeFile:
\r
12759 /* Skip if we know it isn't thinking */
\r
12760 if (!cps->maybeThinking) return;
\r
12761 if (appData.debugMode)
\r
12762 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12763 InterruptChildProcess(cps->pr);
\r
12764 cps->maybeThinking = FALSE;
\r
12769 #endif /*ATTENTION*/
\r
12775 if (whiteTimeRemaining <= 0) {
\r
12776 if (!whiteFlag) {
\r
12777 whiteFlag = TRUE;
\r
12778 if (appData.icsActive) {
\r
12779 if (appData.autoCallFlag &&
\r
12780 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12781 SendToICS(ics_prefix);
\r
12782 SendToICS("flag\n");
\r
12786 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12788 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12789 if (appData.autoCallFlag) {
\r
12790 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12797 if (blackTimeRemaining <= 0) {
\r
12798 if (!blackFlag) {
\r
12799 blackFlag = TRUE;
\r
12800 if (appData.icsActive) {
\r
12801 if (appData.autoCallFlag &&
\r
12802 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12803 SendToICS(ics_prefix);
\r
12804 SendToICS("flag\n");
\r
12808 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12810 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12811 if (appData.autoCallFlag) {
\r
12812 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12823 CheckTimeControl()
\r
12825 if (!appData.clockMode || appData.icsActive ||
\r
12826 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12829 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12831 if ( !WhiteOnMove(forwardMostMove) )
\r
12832 /* White made time control */
\r
12833 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12834 /* [HGM] time odds: correct new time quota for time odds! */
\r
12835 / WhitePlayer()->timeOdds;
\r
12837 /* Black made time control */
\r
12838 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12839 / WhitePlayer()->other->timeOdds;
\r
12843 DisplayBothClocks()
\r
12845 int wom = gameMode == EditPosition ?
\r
12846 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12847 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12848 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12852 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12853 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12854 to use other calls if you don't. Clocks will be less accurate if
\r
12855 you have neither ftime nor gettimeofday.
\r
12858 /* VS 2008 requires the #include outside of the function */
\r
12859 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12860 #include <sys/timeb.h>
\r
12863 /* Get the current time as a TimeMark */
\r
12868 #if HAVE_GETTIMEOFDAY
\r
12870 struct timeval timeVal;
\r
12871 struct timezone timeZone;
\r
12873 gettimeofday(&timeVal, &timeZone);
\r
12874 tm->sec = (long) timeVal.tv_sec;
\r
12875 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12877 #else /*!HAVE_GETTIMEOFDAY*/
\r
12880 // include <sys/timeb.h> / moved to just above start of function
\r
12881 struct timeb timeB;
\r
12884 tm->sec = (long) timeB.time;
\r
12885 tm->ms = (int) timeB.millitm;
\r
12887 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12888 tm->sec = (long) time(NULL);
\r
12894 /* Return the difference in milliseconds between two
\r
12895 time marks. We assume the difference will fit in a long!
\r
12898 SubtractTimeMarks(tm2, tm1)
\r
12899 TimeMark *tm2, *tm1;
\r
12901 return 1000L*(tm2->sec - tm1->sec) +
\r
12902 (long) (tm2->ms - tm1->ms);
\r
12907 * Code to manage the game clocks.
\r
12909 * In tournament play, black starts the clock and then white makes a move.
\r
12910 * We give the human user a slight advantage if he is playing white---the
\r
12911 * clocks don't run until he makes his first move, so it takes zero time.
\r
12912 * Also, we don't account for network lag, so we could get out of sync
\r
12913 * with GNU Chess's clock -- but then, referees are always right.
\r
12916 static TimeMark tickStartTM;
\r
12917 static long intendedTickLength;
\r
12920 NextTickLength(timeRemaining)
\r
12921 long timeRemaining;
\r
12923 long nominalTickLength, nextTickLength;
\r
12925 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12926 nominalTickLength = 100L;
\r
12928 nominalTickLength = 1000L;
\r
12929 nextTickLength = timeRemaining % nominalTickLength;
\r
12930 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12932 return nextTickLength;
\r
12935 /* Adjust clock one minute up or down */
\r
12937 AdjustClock(Boolean which, int dir)
\r
12939 if(which) blackTimeRemaining += 60000*dir;
\r
12940 else whiteTimeRemaining += 60000*dir;
\r
12941 DisplayBothClocks();
\r
12944 /* Stop clocks and reset to a fresh time control */
\r
12948 (void) StopClockTimer();
\r
12949 if (appData.icsActive) {
\r
12950 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12951 } else { /* [HGM] correct new time quote for time odds */
\r
12952 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12953 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12955 if (whiteFlag || blackFlag) {
\r
12956 DisplayTitle("");
\r
12957 whiteFlag = blackFlag = FALSE;
\r
12959 DisplayBothClocks();
\r
12962 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12964 /* Decrement running clock by amount of time that has passed */
\r
12966 DecrementClocks()
\r
12968 long timeRemaining;
\r
12969 long lastTickLength, fudge;
\r
12972 if (!appData.clockMode) return;
\r
12973 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12975 GetTimeMark(&now);
\r
12977 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12979 /* Fudge if we woke up a little too soon */
\r
12980 fudge = intendedTickLength - lastTickLength;
\r
12981 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
12983 if (WhiteOnMove(forwardMostMove)) {
\r
12984 if(whiteNPS >= 0) lastTickLength = 0;
\r
12985 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
12986 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
12987 WhiteOnMove(currentMove));
\r
12989 if(blackNPS >= 0) lastTickLength = 0;
\r
12990 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
12991 DisplayBlackClock(blackTimeRemaining - fudge,
\r
12992 !WhiteOnMove(currentMove));
\r
12995 if (CheckFlags()) return;
\r
12997 tickStartTM = now;
\r
12998 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
12999 StartClockTimer(intendedTickLength);
\r
13001 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13002 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13003 * with increment) the time remaining has increased to a level above the
\r
13004 * threshold, reset the alarm so it can sound again.
\r
13007 if (appData.icsActive && appData.icsAlarm) {
\r
13009 /* make sure we are dealing with the user's clock */
\r
13010 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13011 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13014 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13015 alarmSounded = FALSE;
\r
13016 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13017 PlayAlarmSound();
\r
13018 alarmSounded = TRUE;
\r
13024 /* A player has just moved, so stop the previously running
\r
13025 clock and (if in clock mode) start the other one.
\r
13026 We redisplay both clocks in case we're in ICS mode, because
\r
13027 ICS gives us an update to both clocks after every move.
\r
13028 Note that this routine is called *after* forwardMostMove
\r
13029 is updated, so the last fractional tick must be subtracted
\r
13030 from the color that is *not* on move now.
\r
13035 long lastTickLength;
\r
13037 int flagged = FALSE;
\r
13039 GetTimeMark(&now);
\r
13041 if (StopClockTimer() && appData.clockMode) {
\r
13042 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13043 if (WhiteOnMove(forwardMostMove)) {
\r
13044 if(blackNPS >= 0) lastTickLength = 0;
\r
13045 blackTimeRemaining -= lastTickLength;
\r
13046 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13047 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13048 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13049 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13051 if(whiteNPS >= 0) lastTickLength = 0;
\r
13052 whiteTimeRemaining -= lastTickLength;
\r
13053 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13054 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13055 pvInfoList[forwardMostMove-1].time =
\r
13056 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13058 flagged = CheckFlags();
\r
13060 CheckTimeControl();
\r
13062 if (flagged || !appData.clockMode) return;
\r
13064 switch (gameMode) {
\r
13065 case MachinePlaysBlack:
\r
13066 case MachinePlaysWhite:
\r
13067 case BeginningOfGame:
\r
13068 if (pausing) return;
\r
13072 case PlayFromGameFile:
\r
13073 case IcsExamining:
\r
13080 tickStartTM = now;
\r
13081 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13082 whiteTimeRemaining : blackTimeRemaining);
\r
13083 StartClockTimer(intendedTickLength);
\r
13087 /* Stop both clocks */
\r
13091 long lastTickLength;
\r
13094 if (!StopClockTimer()) return;
\r
13095 if (!appData.clockMode) return;
\r
13097 GetTimeMark(&now);
\r
13099 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13100 if (WhiteOnMove(forwardMostMove)) {
\r
13101 if(whiteNPS >= 0) lastTickLength = 0;
\r
13102 whiteTimeRemaining -= lastTickLength;
\r
13103 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13105 if(blackNPS >= 0) lastTickLength = 0;
\r
13106 blackTimeRemaining -= lastTickLength;
\r
13107 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13112 /* Start clock of player on move. Time may have been reset, so
\r
13113 if clock is already running, stop and restart it. */
\r
13117 (void) StopClockTimer(); /* in case it was running already */
\r
13118 DisplayBothClocks();
\r
13119 if (CheckFlags()) return;
\r
13121 if (!appData.clockMode) return;
\r
13122 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13124 GetTimeMark(&tickStartTM);
\r
13125 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13126 whiteTimeRemaining : blackTimeRemaining);
\r
13128 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13129 whiteNPS = blackNPS = -1;
\r
13130 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13131 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13132 whiteNPS = first.nps;
\r
13133 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13134 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13135 blackNPS = first.nps;
\r
13136 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13137 whiteNPS = second.nps;
\r
13138 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13139 blackNPS = second.nps;
\r
13140 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13142 StartClockTimer(intendedTickLength);
\r
13149 long second, minute, hour, day;
\r
13151 static char buf[32];
\r
13153 if (ms > 0 && ms <= 9900) {
\r
13154 /* convert milliseconds to tenths, rounding up */
\r
13155 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13157 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13161 /* convert milliseconds to seconds, rounding up */
\r
13162 /* use floating point to avoid strangeness of integer division
\r
13163 with negative dividends on many machines */
\r
13164 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13166 if (second < 0) {
\r
13168 second = -second;
\r
13171 day = second / (60 * 60 * 24);
\r
13172 second = second % (60 * 60 * 24);
\r
13173 hour = second / (60 * 60);
\r
13174 second = second % (60 * 60);
\r
13175 minute = second / 60;
\r
13176 second = second % 60;
\r
13179 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13180 sign, day, hour, minute, second);
\r
13181 else if (hour > 0)
\r
13182 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13184 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13191 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13194 StrStr(string, match)
\r
13195 char *string, *match;
\r
13199 length = strlen(match);
\r
13201 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13202 if (!strncmp(match, string, length))
\r
13209 StrCaseStr(string, match)
\r
13210 char *string, *match;
\r
13212 int i, j, length;
\r
13214 length = strlen(match);
\r
13216 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13217 for (j = 0; j < length; j++) {
\r
13218 if (ToLower(match[j]) != ToLower(string[j]))
\r
13221 if (j == length) return string;
\r
13227 #ifndef _amigados
\r
13229 StrCaseCmp(s1, s2)
\r
13235 c1 = ToLower(*s1++);
\r
13236 c2 = ToLower(*s2++);
\r
13237 if (c1 > c2) return 1;
\r
13238 if (c1 < c2) return -1;
\r
13239 if (c1 == NULLCHAR) return 0;
\r
13248 return isupper(c) ? tolower(c) : c;
\r
13256 return islower(c) ? toupper(c) : c;
\r
13258 #endif /* !_amigados */
\r
13266 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13273 StrSavePtr(s, savePtr)
\r
13274 char *s, **savePtr;
\r
13279 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13280 strcpy(*savePtr, s);
\r
13282 return(*savePtr);
\r
13290 char buf[MSG_SIZ];
\r
13292 clock = time((time_t *)NULL);
\r
13293 tm = localtime(&clock);
\r
13294 sprintf(buf, "%04d.%02d.%02d",
\r
13295 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13296 return StrSave(buf);
\r
13301 PositionToFEN(move, overrideCastling)
\r
13303 char *overrideCastling;
\r
13305 int i, j, fromX, fromY, toX, toY;
\r
13310 ChessSquare piece;
\r
13312 whiteToPlay = (gameMode == EditPosition) ?
\r
13313 !blackPlaysFirst : (move % 2 == 0);
\r
13316 /* Piece placement data */
\r
13317 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13319 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13320 if (boards[move][i][j] == EmptySquare) {
\r
13322 } else { ChessSquare piece = boards[move][i][j];
\r
13323 if (emptycount > 0) {
\r
13324 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13325 *p++ = '0' + emptycount;
\r
13326 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13329 if(PieceToChar(piece) == '+') {
\r
13330 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13332 piece = (ChessSquare)(DEMOTED piece);
\r
13334 *p++ = PieceToChar(piece);
\r
13335 if(p[-1] == '~') {
\r
13336 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13337 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13342 if (emptycount > 0) {
\r
13343 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13344 *p++ = '0' + emptycount;
\r
13345 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13352 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13353 if( gameInfo.holdingsWidth ) {
\r
13354 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13356 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13357 piece = boards[move][i][BOARD_WIDTH-1];
\r
13358 if( piece != EmptySquare )
\r
13359 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13360 *p++ = PieceToChar(piece);
\r
13362 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13363 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13364 if( piece != EmptySquare )
\r
13365 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13366 *p++ = PieceToChar(piece);
\r
13369 if( q == p ) *p++ = '-';
\r
13374 /* Active color */
\r
13375 *p++ = whiteToPlay ? 'w' : 'b';
\r
13378 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
\r
13379 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
\r
13381 if(nrCastlingRights) {
\r
13383 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13384 /* [HGM] write directly from rights */
\r
13385 if(castlingRights[move][2] >= 0 &&
\r
13386 castlingRights[move][0] >= 0 )
\r
13387 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13388 if(castlingRights[move][2] >= 0 &&
\r
13389 castlingRights[move][1] >= 0 )
\r
13390 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13391 if(castlingRights[move][5] >= 0 &&
\r
13392 castlingRights[move][3] >= 0 )
\r
13393 *p++ = castlingRights[move][3] + AAA;
\r
13394 if(castlingRights[move][5] >= 0 &&
\r
13395 castlingRights[move][4] >= 0 )
\r
13396 *p++ = castlingRights[move][4] + AAA;
\r
13399 /* [HGM] write true castling rights */
\r
13400 if( nrCastlingRights == 6 ) {
\r
13401 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13402 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13403 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13404 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13405 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13406 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13407 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13408 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13411 if (q == p) *p++ = '-'; /* No castling rights */
\r
13415 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13416 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13417 /* En passant target square */
\r
13418 if (move > backwardMostMove) {
\r
13419 fromX = moveList[move - 1][0] - AAA;
\r
13420 fromY = moveList[move - 1][1] - ONE;
\r
13421 toX = moveList[move - 1][2] - AAA;
\r
13422 toY = moveList[move - 1][3] - ONE;
\r
13423 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13424 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13425 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13427 /* 2-square pawn move just happened */
\r
13428 *p++ = toX + AAA;
\r
13429 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13440 /* [HGM] find reversible plies */
\r
13441 { int i = 0, j=move;
\r
13443 if (appData.debugMode) { int k;
\r
13444 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13445 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13446 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13450 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13451 if( j == backwardMostMove ) i += initialRulePlies;
\r
13452 sprintf(p, "%d ", i);
\r
13453 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13455 /* Fullmove number */
\r
13456 sprintf(p, "%d", (move / 2) + 1);
\r
13458 return StrSave(buf);
\r
13462 ParseFEN(board, blackPlaysFirst, fen)
\r
13464 int *blackPlaysFirst;
\r
13470 ChessSquare piece;
\r
13474 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13475 if(gameInfo.holdingsWidth) {
\r
13476 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13477 board[i][0] = EmptySquare; /* black holdings */
\r
13478 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13479 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13480 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13484 /* Piece placement data */
\r
13485 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13488 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13489 if (*p == '/') p++;
\r
13490 emptycount = gameInfo.boardWidth - j;
\r
13491 while (emptycount--)
\r
13492 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13494 #if(BOARD_SIZE >= 10)
\r
13495 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13496 p++; emptycount=10;
\r
13497 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13498 while (emptycount--)
\r
13499 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13501 } else if (isdigit(*p)) {
\r
13502 emptycount = *p++ - '0';
\r
13503 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13504 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13505 while (emptycount--)
\r
13506 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13507 } else if (*p == '+' || isalpha(*p)) {
\r
13508 if (j >= gameInfo.boardWidth) return FALSE;
\r
13510 piece = CharToPiece(*++p);
\r
13511 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13512 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13513 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13514 } else piece = CharToPiece(*p++);
\r
13516 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13517 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13518 piece = (ChessSquare) (PROMOTED piece);
\r
13519 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13522 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13528 while (*p == '/' || *p == ' ') p++;
\r
13530 /* [HGM] look for Crazyhouse holdings here */
\r
13531 while(*p==' ') p++;
\r
13532 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13533 if(*p == '[') p++;
\r
13534 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13535 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13536 /* if we would allow FEN reading to set board size, we would */
\r
13537 /* have to add holdings and shift the board read so far here */
\r
13538 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13540 if((int) piece >= (int) BlackPawn ) {
\r
13541 i = (int)piece - (int)BlackPawn;
\r
13542 i = PieceToNumber((ChessSquare)i);
\r
13543 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13544 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13545 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13547 i = (int)piece - (int)WhitePawn;
\r
13548 i = PieceToNumber((ChessSquare)i);
\r
13549 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13550 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13551 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13555 if(*p == ']') *p++;
\r
13558 while(*p == ' ') p++;
\r
13560 /* Active color */
\r
13563 *blackPlaysFirst = FALSE;
\r
13566 *blackPlaysFirst = TRUE;
\r
13572 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13573 /* return the extra info in global variiables */
\r
13575 /* set defaults in case FEN is incomplete */
\r
13576 FENepStatus = EP_UNKNOWN;
\r
13577 for(i=0; i<nrCastlingRights; i++ ) {
\r
13578 FENcastlingRights[i] =
\r
13579 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13580 } /* assume possible unless obviously impossible */
\r
13581 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13582 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13583 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13584 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13585 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13586 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13587 FENrulePlies = 0;
\r
13589 while(*p==' ') p++;
\r
13590 if(nrCastlingRights) {
\r
13591 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13592 /* castling indicator present, so default becomes no castlings */
\r
13593 for(i=0; i<nrCastlingRights; i++ ) {
\r
13594 FENcastlingRights[i] = -1;
\r
13597 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13598 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13599 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13600 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13601 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13603 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13604 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13605 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13609 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13610 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13611 FENcastlingRights[2] = whiteKingFile;
\r
13614 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13615 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13616 FENcastlingRights[2] = whiteKingFile;
\r
13619 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13620 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13621 FENcastlingRights[5] = blackKingFile;
\r
13624 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13625 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13626 FENcastlingRights[5] = blackKingFile;
\r
13629 default: /* FRC castlings */
\r
13630 if(c >= 'a') { /* black rights */
\r
13631 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13632 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13633 if(i == BOARD_RGHT) break;
\r
13634 FENcastlingRights[5] = i;
\r
13636 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13637 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13639 FENcastlingRights[3] = c;
\r
13641 FENcastlingRights[4] = c;
\r
13642 } else { /* white rights */
\r
13643 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13644 if(board[0][i] == WhiteKing) break;
\r
13645 if(i == BOARD_RGHT) break;
\r
13646 FENcastlingRights[2] = i;
\r
13647 c -= AAA - 'a' + 'A';
\r
13648 if(board[0][c] >= WhiteKing) break;
\r
13650 FENcastlingRights[0] = c;
\r
13652 FENcastlingRights[1] = c;
\r
13656 if (appData.debugMode) {
\r
13657 fprintf(debugFP, "FEN castling rights:");
\r
13658 for(i=0; i<nrCastlingRights; i++)
\r
13659 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13660 fprintf(debugFP, "\n");
\r
13663 while(*p==' ') p++;
\r
13666 /* read e.p. field in games that know e.p. capture */
\r
13667 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13668 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13670 p++; FENepStatus = EP_NONE;
\r
13672 char c = *p++ - AAA;
\r
13674 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13675 if(*p >= '0' && *p <='9') *p++;
\r
13681 if(sscanf(p, "%d", &i) == 1) {
\r
13682 FENrulePlies = i; /* 50-move ply counter */
\r
13683 /* (The move number is still ignored) */
\r
13690 EditPositionPasteFEN(char *fen)
\r
13692 if (fen != NULL) {
\r
13693 Board initial_position;
\r
13695 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13696 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13699 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13700 EditPositionEvent();
\r
13701 blackPlaysFirst = savedBlackPlaysFirst;
\r
13702 CopyBoard(boards[0], initial_position);
\r
13703 /* [HGM] copy FEN attributes as well */
\r
13705 initialRulePlies = FENrulePlies;
\r
13706 epStatus[0] = FENepStatus;
\r
13707 for( i=0; i<nrCastlingRights; i++ )
\r
13708 castlingRights[0][i] = FENcastlingRights[i];
\r
13710 EditPositionDone();
\r
13711 DisplayBothClocks();
\r
13712 DrawPosition(FALSE, boards[currentMove]);
\r