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 int depth=0; float score;
\r
2280 if(sscanf(parse, "%f/%d", &score, &depth) == 2 && depth>0) {
\r
2281 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
\r
2282 pvInfoList[forwardMostMove-1].depth = depth;
\r
2283 pvInfoList[forwardMostMove-1].score = 100*score;
\r
2285 OutputKibitz(suppressKibitz, parse);
\r
2287 char tmp[MSG_SIZ];
\r
2288 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2289 SendToPlayer(tmp, strlen(tmp));
\r
2292 started = STARTED_NONE;
\r
2294 /* Don't match patterns against characters in chatter */
\r
2299 if (started == STARTED_CHATTER) {
\r
2300 if (buf[i] != '\n') {
\r
2301 /* Don't match patterns against characters in chatter */
\r
2305 started = STARTED_NONE;
\r
2308 /* Kludge to deal with rcmd protocol */
\r
2309 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2310 DisplayFatalError(&buf[1], 0, 1);
\r
2313 firstTime = FALSE;
\r
2316 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2317 ics_type = ICS_ICC;
\r
2319 if (appData.debugMode)
\r
2320 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2323 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2324 ics_type = ICS_FICS;
\r
2326 if (appData.debugMode)
\r
2327 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2330 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2331 ics_type = ICS_CHESSNET;
\r
2333 if (appData.debugMode)
\r
2334 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2339 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2340 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2341 looking_at(buf, &i, "will be \"*\""))) {
\r
2342 strcpy(ics_handle, star_match[0]);
\r
2346 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2347 char buf[MSG_SIZ];
\r
2348 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2349 DisplayIcsInteractionTitle(buf);
\r
2350 have_set_title = TRUE;
\r
2353 /* skip finger notes */
\r
2354 if (started == STARTED_NONE &&
\r
2355 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2356 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2357 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2358 started = STARTED_CHATTER;
\r
2363 /* skip formula vars */
\r
2364 if (started == STARTED_NONE &&
\r
2365 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2366 started = STARTED_CHATTER;
\r
2372 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2373 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2374 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2375 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2376 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2377 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2378 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2379 suppressKibitz = TRUE;
\r
2380 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2381 && (gameMode == IcsPlayingWhite)) ||
\r
2382 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2383 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2384 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2386 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2387 parse_pos = 0; parse[0] = NULLCHAR;
\r
2388 savingComment = TRUE;
\r
2389 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2390 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2394 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2395 started = STARTED_CHATTER;
\r
2396 suppressKibitz = TRUE;
\r
2398 } // [HGM] kibitz: end of patch
\r
2400 if (appData.zippyTalk || appData.zippyPlay) {
\r
2401 /* [DM] Backup address for color zippy lines */
\r
2405 if (loggedOn == TRUE)
\r
2406 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2407 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2409 if (ZippyControl(buf, &i) ||
\r
2410 ZippyConverse(buf, &i) ||
\r
2411 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2413 if (!appData.colorize) continue;
\r
2417 } // [DM] 'else { ' deleted
\r
2418 if (/* Don't color "message" or "messages" output */
\r
2419 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2420 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2421 looking_at(buf, &i, "--* (*:*): ") ||
\r
2422 /* Regular tells and says */
\r
2423 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2424 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2425 looking_at(buf, &i, "* says: ") ||
\r
2426 /* Message notifications (same color as tells) */
\r
2427 looking_at(buf, &i, "* has left a message ") ||
\r
2428 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2429 /* Whispers and kibitzes */
\r
2430 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2431 looking_at(buf, &i, "* kibitzes: ") ||
\r
2432 /* Channel tells */
\r
2433 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2435 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2436 /* Avoid "tells you:" spoofs in channels */
\r
2439 if (star_match[0][0] == NULLCHAR ||
\r
2440 strchr(star_match[0], ' ') ||
\r
2441 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2442 /* Reject bogus matches */
\r
2445 if (appData.colorize) {
\r
2446 if (oldi > next_out) {
\r
2447 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2452 Colorize(ColorTell, FALSE);
\r
2453 curColor = ColorTell;
\r
2456 Colorize(ColorKibitz, FALSE);
\r
2457 curColor = ColorKibitz;
\r
2460 p = strrchr(star_match[1], '(');
\r
2462 p = star_match[1];
\r
2466 if (atoi(p) == 1) {
\r
2467 Colorize(ColorChannel1, FALSE);
\r
2468 curColor = ColorChannel1;
\r
2470 Colorize(ColorChannel, FALSE);
\r
2471 curColor = ColorChannel;
\r
2475 curColor = ColorNormal;
\r
2479 if (started == STARTED_NONE && appData.autoComment &&
\r
2480 (gameMode == IcsObserving ||
\r
2481 gameMode == IcsPlayingWhite ||
\r
2482 gameMode == IcsPlayingBlack)) {
\r
2483 parse_pos = i - oldi;
\r
2484 memcpy(parse, &buf[oldi], parse_pos);
\r
2485 parse[parse_pos] = NULLCHAR;
\r
2486 started = STARTED_COMMENT;
\r
2487 savingComment = TRUE;
\r
2489 started = STARTED_CHATTER;
\r
2490 savingComment = FALSE;
\r
2497 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2498 looking_at(buf, &i, "* c-shouts: ")) {
\r
2499 if (appData.colorize) {
\r
2500 if (oldi > next_out) {
\r
2501 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2504 Colorize(ColorSShout, FALSE);
\r
2505 curColor = ColorSShout;
\r
2508 started = STARTED_CHATTER;
\r
2512 if (looking_at(buf, &i, "--->")) {
\r
2517 if (looking_at(buf, &i, "* shouts: ") ||
\r
2518 looking_at(buf, &i, "--> ")) {
\r
2519 if (appData.colorize) {
\r
2520 if (oldi > next_out) {
\r
2521 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2524 Colorize(ColorShout, FALSE);
\r
2525 curColor = ColorShout;
\r
2528 started = STARTED_CHATTER;
\r
2532 if (looking_at( buf, &i, "Challenge:")) {
\r
2533 if (appData.colorize) {
\r
2534 if (oldi > next_out) {
\r
2535 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2538 Colorize(ColorChallenge, FALSE);
\r
2539 curColor = ColorChallenge;
\r
2545 if (looking_at(buf, &i, "* offers you") ||
\r
2546 looking_at(buf, &i, "* offers to be") ||
\r
2547 looking_at(buf, &i, "* would like to") ||
\r
2548 looking_at(buf, &i, "* requests to") ||
\r
2549 looking_at(buf, &i, "Your opponent offers") ||
\r
2550 looking_at(buf, &i, "Your opponent requests")) {
\r
2552 if (appData.colorize) {
\r
2553 if (oldi > next_out) {
\r
2554 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2557 Colorize(ColorRequest, FALSE);
\r
2558 curColor = ColorRequest;
\r
2563 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2564 if (appData.colorize) {
\r
2565 if (oldi > next_out) {
\r
2566 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2569 Colorize(ColorSeek, FALSE);
\r
2570 curColor = ColorSeek;
\r
2575 if (looking_at(buf, &i, "\\ ")) {
\r
2576 if (prevColor != ColorNormal) {
\r
2577 if (oldi > next_out) {
\r
2578 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2581 Colorize(prevColor, TRUE);
\r
2582 curColor = prevColor;
\r
2584 if (savingComment) {
\r
2585 parse_pos = i - oldi;
\r
2586 memcpy(parse, &buf[oldi], parse_pos);
\r
2587 parse[parse_pos] = NULLCHAR;
\r
2588 started = STARTED_COMMENT;
\r
2590 started = STARTED_CHATTER;
\r
2595 if (looking_at(buf, &i, "Black Strength :") ||
\r
2596 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2597 looking_at(buf, &i, "<10>") ||
\r
2598 looking_at(buf, &i, "#@#")) {
\r
2599 /* Wrong board style */
\r
2601 SendToICS(ics_prefix);
\r
2602 SendToICS("set style 12\n");
\r
2603 SendToICS(ics_prefix);
\r
2604 SendToICS("refresh\n");
\r
2608 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2610 have_sent_ICS_logon = 1;
\r
2614 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2615 (looking_at(buf, &i, "\n<12> ") ||
\r
2616 looking_at(buf, &i, "<12> "))) {
\r
2618 if (oldi > next_out) {
\r
2619 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2622 started = STARTED_BOARD;
\r
2627 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2628 looking_at(buf, &i, "<b1> ")) {
\r
2629 if (oldi > next_out) {
\r
2630 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2633 started = STARTED_HOLDINGS;
\r
2638 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2640 /* Header for a move list -- first line */
\r
2642 switch (ics_getting_history) {
\r
2644 switch (gameMode) {
\r
2646 case BeginningOfGame:
\r
2647 /* User typed "moves" or "oldmoves" while we
\r
2648 were idle. Pretend we asked for these
\r
2649 moves and soak them up so user can step
\r
2650 through them and/or save them.
\r
2652 Reset(FALSE, TRUE);
\r
2653 gameMode = IcsObserving;
\r
2656 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2658 case EditGame: /*?*/
\r
2659 case EditPosition: /*?*/
\r
2660 /* Should above feature work in these modes too? */
\r
2661 /* For now it doesn't */
\r
2662 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2665 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2670 /* Is this the right one? */
\r
2671 if (gameInfo.white && gameInfo.black &&
\r
2672 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2673 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2675 ics_getting_history = H_GOT_REQ_HEADER;
\r
2678 case H_GOT_REQ_HEADER:
\r
2679 case H_GOT_UNREQ_HEADER:
\r
2680 case H_GOT_UNWANTED_HEADER:
\r
2681 case H_GETTING_MOVES:
\r
2682 /* Should not happen */
\r
2683 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2684 ics_getting_history = H_FALSE;
\r
2688 /* Save player ratings into gameInfo if needed */
\r
2689 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2690 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2691 (gameInfo.whiteRating == -1 ||
\r
2692 gameInfo.blackRating == -1)) {
\r
2694 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2695 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2696 if (appData.debugMode)
\r
2697 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2698 gameInfo.whiteRating, gameInfo.blackRating);
\r
2703 if (looking_at(buf, &i,
\r
2704 "* * match, initial time: * minute*, increment: * second")) {
\r
2705 /* Header for a move list -- second line */
\r
2706 /* Initial board will follow if this is a wild game */
\r
2707 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2708 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2709 gameInfo.event = StrSave(str);
\r
2710 /* [HGM] we switched variant. Translate boards if needed. */
\r
2711 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2715 if (looking_at(buf, &i, "Move ")) {
\r
2716 /* Beginning of a move list */
\r
2717 switch (ics_getting_history) {
\r
2719 /* Normally should not happen */
\r
2720 /* Maybe user hit reset while we were parsing */
\r
2723 /* Happens if we are ignoring a move list that is not
\r
2724 * the one we just requested. Common if the user
\r
2725 * tries to observe two games without turning off
\r
2728 case H_GETTING_MOVES:
\r
2729 /* Should not happen */
\r
2730 DisplayError(_("Error gathering move list: nested"), 0);
\r
2731 ics_getting_history = H_FALSE;
\r
2733 case H_GOT_REQ_HEADER:
\r
2734 ics_getting_history = H_GETTING_MOVES;
\r
2735 started = STARTED_MOVES;
\r
2737 if (oldi > next_out) {
\r
2738 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2741 case H_GOT_UNREQ_HEADER:
\r
2742 ics_getting_history = H_GETTING_MOVES;
\r
2743 started = STARTED_MOVES_NOHIDE;
\r
2746 case H_GOT_UNWANTED_HEADER:
\r
2747 ics_getting_history = H_FALSE;
\r
2753 if (looking_at(buf, &i, "% ") ||
\r
2754 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2755 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2756 savingComment = FALSE;
\r
2757 switch (started) {
\r
2758 case STARTED_MOVES:
\r
2759 case STARTED_MOVES_NOHIDE:
\r
2760 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2761 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2762 ParseGameHistory(parse);
\r
2764 if (appData.zippyPlay && first.initDone) {
\r
2765 FeedMovesToProgram(&first, forwardMostMove);
\r
2766 if (gameMode == IcsPlayingWhite) {
\r
2767 if (WhiteOnMove(forwardMostMove)) {
\r
2768 if (first.sendTime) {
\r
2769 if (first.useColors) {
\r
2770 SendToProgram("black\n", &first);
\r
2772 SendTimeRemaining(&first, TRUE);
\r
2775 if (first.useColors) {
\r
2776 SendToProgram("white\ngo\n", &first);
\r
2778 SendToProgram("go\n", &first);
\r
2781 if (first.useColors) {
\r
2782 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2784 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2786 first.maybeThinking = TRUE;
\r
2788 if (first.usePlayother) {
\r
2789 if (first.sendTime) {
\r
2790 SendTimeRemaining(&first, TRUE);
\r
2792 SendToProgram("playother\n", &first);
\r
2793 firstMove = FALSE;
\r
2798 } else if (gameMode == IcsPlayingBlack) {
\r
2799 if (!WhiteOnMove(forwardMostMove)) {
\r
2800 if (first.sendTime) {
\r
2801 if (first.useColors) {
\r
2802 SendToProgram("white\n", &first);
\r
2804 SendTimeRemaining(&first, FALSE);
\r
2807 if (first.useColors) {
\r
2808 SendToProgram("black\ngo\n", &first);
\r
2810 SendToProgram("go\n", &first);
\r
2813 if (first.useColors) {
\r
2814 SendToProgram("black\n", &first);
\r
2816 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2818 first.maybeThinking = TRUE;
\r
2820 if (first.usePlayother) {
\r
2821 if (first.sendTime) {
\r
2822 SendTimeRemaining(&first, FALSE);
\r
2824 SendToProgram("playother\n", &first);
\r
2825 firstMove = FALSE;
\r
2833 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2834 /* Moves came from oldmoves or moves command
\r
2835 while we weren't doing anything else.
\r
2837 currentMove = forwardMostMove;
\r
2838 ClearHighlights();/*!!could figure this out*/
\r
2839 flipView = appData.flipView;
\r
2840 DrawPosition(FALSE, boards[currentMove]);
\r
2841 DisplayBothClocks();
\r
2842 sprintf(str, "%s vs. %s",
\r
2843 gameInfo.white, gameInfo.black);
\r
2844 DisplayTitle(str);
\r
2845 gameMode = IcsIdle;
\r
2847 /* Moves were history of an active game */
\r
2848 if (gameInfo.resultDetails != NULL) {
\r
2849 free(gameInfo.resultDetails);
\r
2850 gameInfo.resultDetails = NULL;
\r
2853 HistorySet(parseList, backwardMostMove,
\r
2854 forwardMostMove, currentMove-1);
\r
2855 DisplayMove(currentMove - 1);
\r
2856 if (started == STARTED_MOVES) next_out = i;
\r
2857 started = STARTED_NONE;
\r
2858 ics_getting_history = H_FALSE;
\r
2861 case STARTED_OBSERVE:
\r
2862 started = STARTED_NONE;
\r
2863 SendToICS(ics_prefix);
\r
2864 SendToICS("refresh\n");
\r
2870 if(bookHit) { // [HGM] book: simulate book reply
\r
2871 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2873 programStats.nodes = programStats.depth = programStats.time =
\r
2874 programStats.score = programStats.got_only_move = 0;
\r
2875 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2877 strcpy(bookMove, "move ");
\r
2878 strcat(bookMove, bookHit);
\r
2879 HandleMachineMove(bookMove, &first);
\r
2884 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2885 started == STARTED_HOLDINGS ||
\r
2886 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2887 /* Accumulate characters in move list or board */
\r
2888 parse[parse_pos++] = buf[i];
\r
2891 /* Start of game messages. Mostly we detect start of game
\r
2892 when the first board image arrives. On some versions
\r
2893 of the ICS, though, we need to do a "refresh" after starting
\r
2894 to observe in order to get the current board right away. */
\r
2895 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2896 started = STARTED_OBSERVE;
\r
2900 /* Handle auto-observe */
\r
2901 if (appData.autoObserve &&
\r
2902 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2903 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2905 /* Choose the player that was highlighted, if any. */
\r
2906 if (star_match[0][0] == '\033' ||
\r
2907 star_match[1][0] != '\033') {
\r
2908 player = star_match[0];
\r
2910 player = star_match[2];
\r
2912 sprintf(str, "%sobserve %s\n",
\r
2913 ics_prefix, StripHighlightAndTitle(player));
\r
2916 /* Save ratings from notify string */
\r
2917 strcpy(player1Name, star_match[0]);
\r
2918 player1Rating = string_to_rating(star_match[1]);
\r
2919 strcpy(player2Name, star_match[2]);
\r
2920 player2Rating = string_to_rating(star_match[3]);
\r
2922 if (appData.debugMode)
\r
2924 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2925 player1Name, player1Rating,
\r
2926 player2Name, player2Rating);
\r
2931 /* Deal with automatic examine mode after a game,
\r
2932 and with IcsObserving -> IcsExamining transition */
\r
2933 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2934 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2936 int gamenum = atoi(star_match[0]);
\r
2937 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2938 gamenum == ics_gamenum) {
\r
2939 /* We were already playing or observing this game;
\r
2940 no need to refetch history */
\r
2941 gameMode = IcsExamining;
\r
2943 pauseExamForwardMostMove = forwardMostMove;
\r
2944 } else if (currentMove < forwardMostMove) {
\r
2945 ForwardInner(forwardMostMove);
\r
2948 /* I don't think this case really can happen */
\r
2949 SendToICS(ics_prefix);
\r
2950 SendToICS("refresh\n");
\r
2955 /* Error messages */
\r
2956 if (ics_user_moved) {
\r
2957 if (looking_at(buf, &i, "Illegal move") ||
\r
2958 looking_at(buf, &i, "Not a legal move") ||
\r
2959 looking_at(buf, &i, "Your king is in check") ||
\r
2960 looking_at(buf, &i, "It isn't your turn") ||
\r
2961 looking_at(buf, &i, "It is not your move")) {
\r
2962 /* Illegal move */
\r
2963 ics_user_moved = 0;
\r
2964 if (forwardMostMove > backwardMostMove) {
\r
2965 currentMove = --forwardMostMove;
\r
2966 DisplayMove(currentMove - 1); /* before DMError */
\r
2967 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2968 DrawPosition(FALSE, boards[currentMove]);
\r
2970 DisplayBothClocks();
\r
2976 if (looking_at(buf, &i, "still have time") ||
\r
2977 looking_at(buf, &i, "not out of time") ||
\r
2978 looking_at(buf, &i, "either player is out of time") ||
\r
2979 looking_at(buf, &i, "has timeseal; checking")) {
\r
2980 /* We must have called his flag a little too soon */
\r
2981 whiteFlag = blackFlag = FALSE;
\r
2985 if (looking_at(buf, &i, "added * seconds to") ||
\r
2986 looking_at(buf, &i, "seconds were added to")) {
\r
2987 /* Update the clocks */
\r
2988 SendToICS(ics_prefix);
\r
2989 SendToICS("refresh\n");
\r
2993 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2994 ics_clock_paused = TRUE;
\r
2999 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
3000 ics_clock_paused = FALSE;
\r
3005 /* Grab player ratings from the Creating: message.
\r
3006 Note we have to check for the special case when
\r
3007 the ICS inserts things like [white] or [black]. */
\r
3008 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
3009 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3011 0 player 1 name (not necessarily white)
\r
3013 2 empty, white, or black (IGNORED)
\r
3014 3 player 2 name (not necessarily black)
\r
3017 The names/ratings are sorted out when the game
\r
3018 actually starts (below).
\r
3020 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3021 player1Rating = string_to_rating(star_match[1]);
\r
3022 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3023 player2Rating = string_to_rating(star_match[4]);
\r
3025 if (appData.debugMode)
\r
3027 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3028 player1Name, player1Rating,
\r
3029 player2Name, player2Rating);
\r
3034 /* Improved generic start/end-of-game messages */
\r
3035 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3036 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3037 /* If tkind == 0: */
\r
3038 /* star_match[0] is the game number */
\r
3039 /* [1] is the white player's name */
\r
3040 /* [2] is the black player's name */
\r
3041 /* For end-of-game: */
\r
3042 /* [3] is the reason for the game end */
\r
3043 /* [4] is a PGN end game-token, preceded by " " */
\r
3044 /* For start-of-game: */
\r
3045 /* [3] begins with "Creating" or "Continuing" */
\r
3046 /* [4] is " *" or empty (don't care). */
\r
3047 int gamenum = atoi(star_match[0]);
\r
3048 char *whitename, *blackname, *why, *endtoken;
\r
3049 ChessMove endtype = (ChessMove) 0;
\r
3052 whitename = star_match[1];
\r
3053 blackname = star_match[2];
\r
3054 why = star_match[3];
\r
3055 endtoken = star_match[4];
\r
3057 whitename = star_match[1];
\r
3058 blackname = star_match[3];
\r
3059 why = star_match[5];
\r
3060 endtoken = star_match[6];
\r
3063 /* Game start messages */
\r
3064 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3065 strncmp(why, "Continuing ", 11) == 0) {
\r
3066 gs_gamenum = gamenum;
\r
3067 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3069 if (appData.zippyPlay) {
\r
3070 ZippyGameStart(whitename, blackname);
\r
3076 /* Game end messages */
\r
3077 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3078 ics_gamenum != gamenum) {
\r
3081 while (endtoken[0] == ' ') endtoken++;
\r
3082 switch (endtoken[0]) {
\r
3085 endtype = GameUnfinished;
\r
3088 endtype = BlackWins;
\r
3091 if (endtoken[1] == '/')
\r
3092 endtype = GameIsDrawn;
\r
3094 endtype = WhiteWins;
\r
3097 GameEnds(endtype, why, GE_ICS);
\r
3099 if (appData.zippyPlay && first.initDone) {
\r
3100 ZippyGameEnd(endtype, why);
\r
3101 if (first.pr == NULL) {
\r
3102 /* Start the next process early so that we'll
\r
3103 be ready for the next challenge */
\r
3104 StartChessProgram(&first);
\r
3106 /* Send "new" early, in case this command takes
\r
3107 a long time to finish, so that we'll be ready
\r
3108 for the next challenge. */
\r
3109 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3110 Reset(TRUE, TRUE);
\r
3116 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3117 looking_at(buf, &i, "no longer observing game *") ||
\r
3118 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3119 if (gameMode == IcsObserving &&
\r
3120 atoi(star_match[0]) == ics_gamenum)
\r
3122 /* icsEngineAnalyze */
\r
3123 if (appData.icsEngineAnalyze) {
\r
3124 ExitAnalyzeMode();
\r
3128 gameMode = IcsIdle;
\r
3130 ics_user_moved = FALSE;
\r
3135 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3136 if (gameMode == IcsExamining &&
\r
3137 atoi(star_match[0]) == ics_gamenum)
\r
3139 gameMode = IcsIdle;
\r
3141 ics_user_moved = FALSE;
\r
3146 /* Advance leftover_start past any newlines we find,
\r
3147 so only partial lines can get reparsed */
\r
3148 if (looking_at(buf, &i, "\n")) {
\r
3149 prevColor = curColor;
\r
3150 if (curColor != ColorNormal) {
\r
3151 if (oldi > next_out) {
\r
3152 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3155 Colorize(ColorNormal, FALSE);
\r
3156 curColor = ColorNormal;
\r
3158 if (started == STARTED_BOARD) {
\r
3159 started = STARTED_NONE;
\r
3160 parse[parse_pos] = NULLCHAR;
\r
3161 ParseBoard12(parse);
\r
3162 ics_user_moved = 0;
\r
3164 /* Send premove here */
\r
3165 if (appData.premove) {
\r
3166 char str[MSG_SIZ];
\r
3167 if (currentMove == 0 &&
\r
3168 gameMode == IcsPlayingWhite &&
\r
3169 appData.premoveWhite) {
\r
3170 sprintf(str, "%s%s\n", ics_prefix,
\r
3171 appData.premoveWhiteText);
\r
3172 if (appData.debugMode)
\r
3173 fprintf(debugFP, "Sending premove:\n");
\r
3175 } else if (currentMove == 1 &&
\r
3176 gameMode == IcsPlayingBlack &&
\r
3177 appData.premoveBlack) {
\r
3178 sprintf(str, "%s%s\n", ics_prefix,
\r
3179 appData.premoveBlackText);
\r
3180 if (appData.debugMode)
\r
3181 fprintf(debugFP, "Sending premove:\n");
\r
3183 } else if (gotPremove) {
\r
3185 ClearPremoveHighlights();
\r
3186 if (appData.debugMode)
\r
3187 fprintf(debugFP, "Sending premove:\n");
\r
3188 UserMoveEvent(premoveFromX, premoveFromY,
\r
3189 premoveToX, premoveToY,
\r
3190 premovePromoChar);
\r
3194 /* Usually suppress following prompt */
\r
3195 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3196 if (looking_at(buf, &i, "*% ")) {
\r
3197 savingComment = FALSE;
\r
3201 } else if (started == STARTED_HOLDINGS) {
\r
3203 char new_piece[MSG_SIZ];
\r
3204 started = STARTED_NONE;
\r
3205 parse[parse_pos] = NULLCHAR;
\r
3206 if (appData.debugMode)
\r
3207 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3208 parse, currentMove);
\r
3209 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3210 gamenum == ics_gamenum) {
\r
3211 if (gameInfo.variant == VariantNormal) {
\r
3212 /* [HGM] We seem to switch variant during a game!
\r
3213 * Presumably no holdings were displayed, so we have
\r
3214 * to move the position two files to the right to
\r
3215 * create room for them!
\r
3217 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3218 /* Get a move list just to see the header, which
\r
3219 will tell us whether this is really bug or zh */
\r
3220 if (ics_getting_history == H_FALSE) {
\r
3221 ics_getting_history = H_REQUESTED;
\r
3222 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3226 new_piece[0] = NULLCHAR;
\r
3227 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3228 &gamenum, white_holding, black_holding,
\r
3230 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3231 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3232 /* [HGM] copy holdings to board holdings area */
\r
3233 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3234 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3236 if (appData.zippyPlay && first.initDone) {
\r
3237 ZippyHoldings(white_holding, black_holding,
\r
3241 if (tinyLayout || smallLayout) {
\r
3242 char wh[16], bh[16];
\r
3243 PackHolding(wh, white_holding);
\r
3244 PackHolding(bh, black_holding);
\r
3245 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3246 gameInfo.white, gameInfo.black);
\r
3248 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3249 gameInfo.white, white_holding,
\r
3250 gameInfo.black, black_holding);
\r
3253 DrawPosition(FALSE, boards[currentMove]);
\r
3254 DisplayTitle(str);
\r
3256 /* Suppress following prompt */
\r
3257 if (looking_at(buf, &i, "*% ")) {
\r
3258 savingComment = FALSE;
\r
3265 i++; /* skip unparsed character and loop back */
\r
3268 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3269 started != STARTED_HOLDINGS && i > next_out) {
\r
3270 SendToPlayer(&buf[next_out], i - next_out);
\r
3273 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3275 leftover_len = buf_len - leftover_start;
\r
3276 /* if buffer ends with something we couldn't parse,
\r
3277 reparse it after appending the next read */
\r
3279 } else if (count == 0) {
\r
3280 RemoveInputSource(isr);
\r
3281 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3283 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3288 /* Board style 12 looks like this:
\r
3290 <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
3292 * The "<12> " is stripped before it gets to this routine. The two
\r
3293 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3294 * some chess servers may not have them, or may have only the first.
\r
3295 * Additional trailing fields may be added in the future.
\r
3298 #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
3300 #define RELATION_OBSERVING_PLAYED 0
\r
3301 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3302 #define RELATION_PLAYING_MYMOVE 1
\r
3303 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3304 #define RELATION_EXAMINING 2
\r
3305 #define RELATION_ISOLATED_BOARD -3
\r
3306 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3309 ParseBoard12(string)
\r
3312 GameMode newGameMode;
\r
3313 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3314 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3315 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3316 char to_play, board_chars[200];
\r
3317 char move_str[500], str[500], elapsed_time[500];
\r
3318 char black[32], white[32];
\r
3320 int prevMove = currentMove;
\r
3322 ChessMove moveType;
\r
3323 int fromX, fromY, toX, toY;
\r
3325 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3326 char *bookHit = NULL; // [HGM] book
\r
3328 fromX = fromY = toX = toY = -1;
\r
3332 if (appData.debugMode)
\r
3333 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3335 move_str[0] = NULLCHAR;
\r
3336 elapsed_time[0] = NULLCHAR;
\r
3337 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3339 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3340 if(string[i] == ' ') { ranks++; files = 0; }
\r
3344 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3345 board_chars[i] = '\0';
\r
3348 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3349 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3350 &gamenum, white, black, &relation, &basetime, &increment,
\r
3351 &white_stren, &black_stren, &white_time, &black_time,
\r
3352 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3356 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3357 DisplayError(str, 0);
\r
3361 /* Convert the move number to internal form */
\r
3362 moveNum = (moveNum - 1) * 2;
\r
3363 if (to_play == 'B') moveNum++;
\r
3364 if (moveNum >= MAX_MOVES) {
\r
3365 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3370 switch (relation) {
\r
3371 case RELATION_OBSERVING_PLAYED:
\r
3372 case RELATION_OBSERVING_STATIC:
\r
3373 if (gamenum == -1) {
\r
3374 /* Old ICC buglet */
\r
3375 relation = RELATION_OBSERVING_STATIC;
\r
3377 newGameMode = IcsObserving;
\r
3379 case RELATION_PLAYING_MYMOVE:
\r
3380 case RELATION_PLAYING_NOTMYMOVE:
\r
3382 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3383 IcsPlayingWhite : IcsPlayingBlack;
\r
3385 case RELATION_EXAMINING:
\r
3386 newGameMode = IcsExamining;
\r
3388 case RELATION_ISOLATED_BOARD:
\r
3390 /* Just display this board. If user was doing something else,
\r
3391 we will forget about it until the next board comes. */
\r
3392 newGameMode = IcsIdle;
\r
3394 case RELATION_STARTING_POSITION:
\r
3395 newGameMode = gameMode;
\r
3399 /* Modify behavior for initial board display on move listing
\r
3402 switch (ics_getting_history) {
\r
3406 case H_GOT_REQ_HEADER:
\r
3407 case H_GOT_UNREQ_HEADER:
\r
3408 /* This is the initial position of the current game */
\r
3409 gamenum = ics_gamenum;
\r
3410 moveNum = 0; /* old ICS bug workaround */
\r
3411 if (to_play == 'B') {
\r
3412 startedFromSetupPosition = TRUE;
\r
3413 blackPlaysFirst = TRUE;
\r
3415 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3416 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3417 if (currentMove == 0) currentMove = 1;
\r
3419 newGameMode = gameMode;
\r
3420 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3422 case H_GOT_UNWANTED_HEADER:
\r
3423 /* This is an initial board that we don't want */
\r
3425 case H_GETTING_MOVES:
\r
3426 /* Should not happen */
\r
3427 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3428 ics_getting_history = H_FALSE;
\r
3432 /* Take action if this is the first board of a new game, or of a
\r
3433 different game than is currently being displayed. */
\r
3434 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3435 relation == RELATION_ISOLATED_BOARD) {
\r
3437 /* Forget the old game and get the history (if any) of the new one */
\r
3438 if (gameMode != BeginningOfGame) {
\r
3439 Reset(FALSE, TRUE);
\r
3442 if (appData.autoRaiseBoard) BoardToTop();
\r
3444 if (gamenum == -1) {
\r
3445 newGameMode = IcsIdle;
\r
3446 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3447 appData.getMoveList) {
\r
3448 /* Need to get game history */
\r
3449 ics_getting_history = H_REQUESTED;
\r
3450 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3454 /* Initially flip the board to have black on the bottom if playing
\r
3455 black or if the ICS flip flag is set, but let the user change
\r
3456 it with the Flip View button. */
\r
3457 flipView = appData.autoFlipView ?
\r
3458 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3461 /* Done with values from previous mode; copy in new ones */
\r
3462 gameMode = newGameMode;
\r
3464 ics_gamenum = gamenum;
\r
3465 if (gamenum == gs_gamenum) {
\r
3466 int klen = strlen(gs_kind);
\r
3467 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3468 sprintf(str, "ICS %s", gs_kind);
\r
3469 gameInfo.event = StrSave(str);
\r
3471 gameInfo.event = StrSave("ICS game");
\r
3473 gameInfo.site = StrSave(appData.icsHost);
\r
3474 gameInfo.date = PGNDate();
\r
3475 gameInfo.round = StrSave("-");
\r
3476 gameInfo.white = StrSave(white);
\r
3477 gameInfo.black = StrSave(black);
\r
3478 timeControl = basetime * 60 * 1000;
\r
3479 timeControl_2 = 0;
\r
3480 timeIncrement = increment * 1000;
\r
3481 movesPerSession = 0;
\r
3482 gameInfo.timeControl = TimeControlTagValue();
\r
3483 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3484 if (appData.debugMode) {
\r
3485 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3486 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3487 setbuf(debugFP, NULL);
\r
3490 gameInfo.outOfBook = NULL;
\r
3492 /* Do we have the ratings? */
\r
3493 if (strcmp(player1Name, white) == 0 &&
\r
3494 strcmp(player2Name, black) == 0) {
\r
3495 if (appData.debugMode)
\r
3496 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3497 player1Rating, player2Rating);
\r
3498 gameInfo.whiteRating = player1Rating;
\r
3499 gameInfo.blackRating = player2Rating;
\r
3500 } else if (strcmp(player2Name, white) == 0 &&
\r
3501 strcmp(player1Name, black) == 0) {
\r
3502 if (appData.debugMode)
\r
3503 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3504 player2Rating, player1Rating);
\r
3505 gameInfo.whiteRating = player2Rating;
\r
3506 gameInfo.blackRating = player1Rating;
\r
3508 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3510 /* Silence shouts if requested */
\r
3511 if (appData.quietPlay &&
\r
3512 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3513 SendToICS(ics_prefix);
\r
3514 SendToICS("set shout 0\n");
\r
3518 /* Deal with midgame name changes */
\r
3520 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3521 if (gameInfo.white) free(gameInfo.white);
\r
3522 gameInfo.white = StrSave(white);
\r
3524 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3525 if (gameInfo.black) free(gameInfo.black);
\r
3526 gameInfo.black = StrSave(black);
\r
3530 /* Throw away game result if anything actually changes in examine mode */
\r
3531 if (gameMode == IcsExamining && !newGame) {
\r
3532 gameInfo.result = GameUnfinished;
\r
3533 if (gameInfo.resultDetails != NULL) {
\r
3534 free(gameInfo.resultDetails);
\r
3535 gameInfo.resultDetails = NULL;
\r
3539 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3540 in if they are in a different variation than we are. */
\r
3541 if (pauseExamInvalid) return;
\r
3542 if (pausing && gameMode == IcsExamining) {
\r
3543 if (moveNum <= pauseExamForwardMostMove) {
\r
3544 pauseExamInvalid = TRUE;
\r
3545 forwardMostMove = pauseExamForwardMostMove;
\r
3550 if (appData.debugMode) {
\r
3551 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3553 /* Parse the board */
\r
3554 for (k = 0; k < ranks; k++) {
\r
3555 for (j = 0; j < files; j++)
\r
3556 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3557 if(gameInfo.holdingsWidth > 1) {
\r
3558 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3559 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3562 CopyBoard(boards[moveNum], board);
\r
3563 if (moveNum == 0) {
\r
3564 startedFromSetupPosition =
\r
3565 !CompareBoards(board, initialPosition);
\r
3566 if(startedFromSetupPosition)
\r
3567 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3570 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3571 to make it also work for FRC opening positions. Note that board12
\r
3572 is really defective for later FRC positions, as it has no way to
\r
3573 indicate which Rook can castle if they are on the same side of King.
\r
3574 For the initial position we grant rights to the outermost Rooks,
\r
3575 and remember thos rights, and we then copy them on positions
\r
3576 later in an FRC game. This means WB might not recognize castlings with
\r
3577 Rooks that have moved back to their original position as illegal,
\r
3578 but in ICS mode that is not its job anyway.
\r
3580 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3581 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3583 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3584 if(board[0][i] == WhiteRook) j = i;
\r
3585 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3586 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3587 if(board[0][i] == WhiteRook) j = i;
\r
3588 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3589 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3590 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3591 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3592 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3594 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3596 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3597 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3598 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3599 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3600 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3601 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3603 r = castlingRights[moveNum][0] = initialRights[0];
\r
3604 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3605 r = castlingRights[moveNum][1] = initialRights[1];
\r
3606 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3607 r = castlingRights[moveNum][3] = initialRights[3];
\r
3608 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3609 r = castlingRights[moveNum][4] = initialRights[4];
\r
3610 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3611 /* wildcastle kludge: always assume King has rights */
\r
3612 r = castlingRights[moveNum][2] = initialRights[2];
\r
3613 r = castlingRights[moveNum][5] = initialRights[5];
\r
3615 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3616 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3619 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3620 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3621 /* This was an initial position from a move list, not
\r
3622 the current position */
\r
3626 /* Update currentMove and known move number limits */
\r
3627 newMove = newGame || moveNum > forwardMostMove;
\r
3629 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3630 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3631 takeback = forwardMostMove - moveNum;
\r
3632 for (i = 0; i < takeback; i++) {
\r
3633 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3634 SendToProgram("undo\n", &first);
\r
3639 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3640 if (gameMode == IcsExamining && moveNum == 0) {
\r
3641 /* Workaround for ICS limitation: we are not told the wild
\r
3642 type when starting to examine a game. But if we ask for
\r
3643 the move list, the move list header will tell us */
\r
3644 ics_getting_history = H_REQUESTED;
\r
3645 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3648 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3649 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3650 forwardMostMove = moveNum;
\r
3651 if (!pausing || currentMove > forwardMostMove)
\r
3652 currentMove = forwardMostMove;
\r
3654 /* New part of history that is not contiguous with old part */
\r
3655 if (pausing && gameMode == IcsExamining) {
\r
3656 pauseExamInvalid = TRUE;
\r
3657 forwardMostMove = pauseExamForwardMostMove;
\r
3660 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3661 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3662 ics_getting_history = H_REQUESTED;
\r
3663 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3668 /* Update the clocks */
\r
3669 if (strchr(elapsed_time, '.')) {
\r
3670 /* Time is in ms */
\r
3671 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3672 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3674 /* Time is in seconds */
\r
3675 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3676 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3681 if (appData.zippyPlay && newGame &&
\r
3682 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3683 gameMode != IcsExamining)
\r
3684 ZippyFirstBoard(moveNum, basetime, increment);
\r
3687 /* Put the move on the move list, first converting
\r
3688 to canonical algebraic form. */
\r
3689 if (moveNum > 0) {
\r
3690 if (appData.debugMode) {
\r
3691 if (appData.debugMode) { int f = forwardMostMove;
\r
3692 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3693 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3695 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3696 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3697 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3698 setbuf(debugFP, NULL);
\r
3700 if (moveNum <= backwardMostMove) {
\r
3701 /* We don't know what the board looked like before
\r
3702 this move. Punt. */
\r
3703 strcpy(parseList[moveNum - 1], move_str);
\r
3704 strcat(parseList[moveNum - 1], " ");
\r
3705 strcat(parseList[moveNum - 1], elapsed_time);
\r
3706 moveList[moveNum - 1][0] = NULLCHAR;
\r
3707 } else if (strcmp(move_str, "none") == 0) {
\r
3708 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3709 /* Again, we don't know what the board looked like;
\r
3710 this is really the start of the game. */
\r
3711 parseList[moveNum - 1][0] = NULLCHAR;
\r
3712 moveList[moveNum - 1][0] = NULLCHAR;
\r
3713 backwardMostMove = moveNum;
\r
3714 startedFromSetupPosition = TRUE;
\r
3715 fromX = fromY = toX = toY = -1;
\r
3717 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3718 // So we parse the long-algebraic move string in stead of the SAN move
\r
3719 int valid; char buf[MSG_SIZ], *prom;
\r
3721 // str looks something like "Q/a1-a2"; kill the slash
\r
3722 if(str[1] == '/')
\r
3723 sprintf(buf, "%c%s", str[0], str+2);
\r
3724 else strcpy(buf, str); // might be castling
\r
3725 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3726 strcat(buf, prom); // long move lacks promo specification!
\r
3727 if(!appData.testLegality) {
\r
3728 if(appData.debugMode)
\r
3729 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3730 strcpy(move_str, buf);
\r
3732 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3733 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3734 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3735 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3736 // end of long SAN patch
\r
3738 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3739 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3740 fromY, fromX, toY, toX, promoChar,
\r
3741 parseList[moveNum-1]);
\r
3742 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3743 castlingRights[moveNum]) ) {
\r
3745 case MT_STALEMATE:
\r
3749 if(gameInfo.variant != VariantShogi)
\r
3750 strcat(parseList[moveNum - 1], "+");
\r
3752 case MT_CHECKMATE:
\r
3753 strcat(parseList[moveNum - 1], "#");
\r
3756 strcat(parseList[moveNum - 1], " ");
\r
3757 strcat(parseList[moveNum - 1], elapsed_time);
\r
3758 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3759 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3760 strcat(moveList[moveNum - 1], "\n");
\r
3762 /* Move from ICS was illegal!? Punt. */
\r
3763 if (appData.debugMode) {
\r
3764 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3765 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3768 if (appData.testLegality && appData.debugMode) {
\r
3769 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3770 DisplayError(str, 0);
\r
3773 strcpy(parseList[moveNum - 1], move_str);
\r
3774 strcat(parseList[moveNum - 1], " ");
\r
3775 strcat(parseList[moveNum - 1], elapsed_time);
\r
3776 moveList[moveNum - 1][0] = NULLCHAR;
\r
3777 fromX = fromY = toX = toY = -1;
\r
3780 if (appData.debugMode) {
\r
3781 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3782 setbuf(debugFP, NULL);
\r
3786 /* Send move to chess program (BEFORE animating it). */
\r
3787 if (appData.zippyPlay && !newGame && newMove &&
\r
3788 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3790 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3791 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3792 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3793 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3795 DisplayError(str, 0);
\r
3797 if (first.sendTime) {
\r
3798 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3800 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3801 if (firstMove && !bookHit) {
\r
3802 firstMove = FALSE;
\r
3803 if (first.useColors) {
\r
3804 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3806 "black\ngo\n", &first);
\r
3808 SendToProgram("go\n", &first);
\r
3810 first.maybeThinking = TRUE;
\r
3813 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3814 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3815 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3816 DisplayError(str, 0);
\r
3818 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3819 SendMoveToProgram(moveNum - 1, &first);
\r
3826 if (moveNum > 0 && !gotPremove) {
\r
3827 /* If move comes from a remote source, animate it. If it
\r
3828 isn't remote, it will have already been animated. */
\r
3829 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3830 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3832 if (!pausing && appData.highlightLastMove) {
\r
3833 SetHighlights(fromX, fromY, toX, toY);
\r
3837 /* Start the clocks */
\r
3838 whiteFlag = blackFlag = FALSE;
\r
3839 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3840 if (ticking == 0) {
\r
3841 ics_clock_paused = TRUE;
\r
3843 } else if (ticking == 1) {
\r
3844 ics_clock_paused = FALSE;
\r
3846 if (gameMode == IcsIdle ||
\r
3847 relation == RELATION_OBSERVING_STATIC ||
\r
3848 relation == RELATION_EXAMINING ||
\r
3850 DisplayBothClocks();
\r
3854 /* Display opponents and material strengths */
\r
3855 if (gameInfo.variant != VariantBughouse &&
\r
3856 gameInfo.variant != VariantCrazyhouse) {
\r
3857 if (tinyLayout || smallLayout) {
\r
3858 if(gameInfo.variant == VariantNormal)
\r
3859 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3860 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3861 basetime, increment);
\r
3863 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3864 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3865 basetime, increment, (int) gameInfo.variant);
\r
3867 if(gameInfo.variant == VariantNormal)
\r
3868 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3869 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3870 basetime, increment);
\r
3872 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3873 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3874 basetime, increment, VariantName(gameInfo.variant));
\r
3876 DisplayTitle(str);
\r
3877 if (appData.debugMode) {
\r
3878 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3883 /* Display the board */
\r
3886 if (appData.premove)
\r
3887 if (!gotPremove ||
\r
3888 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3889 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3890 ClearPremoveHighlights();
\r
3892 DrawPosition(FALSE, boards[currentMove]);
\r
3893 DisplayMove(moveNum - 1);
\r
3894 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3898 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3900 if(bookHit) { // [HGM] book: simulate book reply
\r
3901 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3903 programStats.nodes = programStats.depth = programStats.time =
\r
3904 programStats.score = programStats.got_only_move = 0;
\r
3905 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3907 strcpy(bookMove, "move ");
\r
3908 strcat(bookMove, bookHit);
\r
3909 HandleMachineMove(bookMove, &first);
\r
3915 GetMoveListEvent()
\r
3917 char buf[MSG_SIZ];
\r
3918 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3919 ics_getting_history = H_REQUESTED;
\r
3920 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3926 AnalysisPeriodicEvent(force)
\r
3929 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3930 && !force) || !appData.periodicUpdates)
\r
3933 /* Send . command to Crafty to collect stats */
\r
3934 SendToProgram(".\n", &first);
\r
3936 /* Don't send another until we get a response (this makes
\r
3937 us stop sending to old Crafty's which don't understand
\r
3938 the "." command (sending illegal cmds resets node count & time,
\r
3939 which looks bad)) */
\r
3940 programStats.ok_to_send = 0;
\r
3944 SendMoveToProgram(moveNum, cps)
\r
3946 ChessProgramState *cps;
\r
3948 char buf[MSG_SIZ];
\r
3950 if (cps->useUsermove) {
\r
3951 SendToProgram("usermove ", cps);
\r
3953 if (cps->useSAN) {
\r
3955 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3956 int len = space - parseList[moveNum];
\r
3957 memcpy(buf, parseList[moveNum], len);
\r
3958 buf[len++] = '\n';
\r
3959 buf[len] = NULLCHAR;
\r
3961 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3963 SendToProgram(buf, cps);
\r
3965 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3966 AlphaRank(moveList[moveNum], 4);
\r
3967 SendToProgram(moveList[moveNum], cps);
\r
3968 AlphaRank(moveList[moveNum], 4); // and back
\r
3970 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3971 * the engine. It would be nice to have a better way to identify castle
\r
3973 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3974 && cps->useOOCastle) {
\r
3975 int fromX = moveList[moveNum][0] - AAA;
\r
3976 int fromY = moveList[moveNum][1] - ONE;
\r
3977 int toX = moveList[moveNum][2] - AAA;
\r
3978 int toY = moveList[moveNum][3] - ONE;
\r
3979 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3980 && boards[moveNum][toY][toX] == WhiteRook)
\r
3981 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3982 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3983 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3984 else SendToProgram("O-O-O\n", cps);
\r
3986 else SendToProgram(moveList[moveNum], cps);
\r
3988 else SendToProgram(moveList[moveNum], cps);
\r
3989 /* End of additions by Tord */
\r
3992 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3993 /* Send 'go' if we are in a mode where machine should play. */
\r
3994 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3995 (gameMode == TwoMachinesPlay ||
\r
3997 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3999 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
4000 SendToProgram("go\n", cps);
\r
4001 if (appData.debugMode) {
\r
4002 fprintf(debugFP, "(extra)\n");
\r
4005 setboardSpoiledMachineBlack = 0;
\r
4009 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
4010 ChessMove moveType;
\r
4011 int fromX, fromY, toX, toY;
\r
4013 char user_move[MSG_SIZ];
\r
4015 switch (moveType) {
\r
4017 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4018 (int)moveType, fromX, fromY, toX, toY);
\r
4019 DisplayError(user_move + strlen("say "), 0);
\r
4021 case WhiteKingSideCastle:
\r
4022 case BlackKingSideCastle:
\r
4023 case WhiteQueenSideCastleWild:
\r
4024 case BlackQueenSideCastleWild:
\r
4026 case WhiteHSideCastleFR:
\r
4027 case BlackHSideCastleFR:
\r
4029 sprintf(user_move, "o-o\n");
\r
4031 case WhiteQueenSideCastle:
\r
4032 case BlackQueenSideCastle:
\r
4033 case WhiteKingSideCastleWild:
\r
4034 case BlackKingSideCastleWild:
\r
4036 case WhiteASideCastleFR:
\r
4037 case BlackASideCastleFR:
\r
4039 sprintf(user_move, "o-o-o\n");
\r
4041 case WhitePromotionQueen:
\r
4042 case BlackPromotionQueen:
\r
4043 case WhitePromotionRook:
\r
4044 case BlackPromotionRook:
\r
4045 case WhitePromotionBishop:
\r
4046 case BlackPromotionBishop:
\r
4047 case WhitePromotionKnight:
\r
4048 case BlackPromotionKnight:
\r
4049 case WhitePromotionKing:
\r
4050 case BlackPromotionKing:
\r
4051 case WhitePromotionChancellor:
\r
4052 case BlackPromotionChancellor:
\r
4053 case WhitePromotionArchbishop:
\r
4054 case BlackPromotionArchbishop:
\r
4055 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4056 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4057 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4058 PieceToChar(WhiteFerz));
\r
4059 else if(gameInfo.variant == VariantGreat)
\r
4060 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4061 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4062 PieceToChar(WhiteMan));
\r
4064 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4065 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4066 PieceToChar(PromoPiece(moveType)));
\r
4070 sprintf(user_move, "%c@%c%c\n",
\r
4071 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4072 AAA + toX, ONE + toY);
\r
4075 case WhiteCapturesEnPassant:
\r
4076 case BlackCapturesEnPassant:
\r
4077 case IllegalMove: /* could be a variant we don't quite understand */
\r
4078 sprintf(user_move, "%c%c%c%c\n",
\r
4079 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4082 SendToICS(user_move);
\r
4086 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4087 int rf, ff, rt, ft;
\r
4091 if (rf == DROP_RANK) {
\r
4092 sprintf(move, "%c@%c%c\n",
\r
4093 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4095 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4096 sprintf(move, "%c%c%c%c\n",
\r
4097 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4099 sprintf(move, "%c%c%c%c%c\n",
\r
4100 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4106 ProcessICSInitScript(f)
\r
4109 char buf[MSG_SIZ];
\r
4111 while (fgets(buf, MSG_SIZ, f)) {
\r
4112 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4119 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4121 AlphaRank(char *move, int n)
\r
4123 // char *p = move, c; int x, y;
\r
4125 if (appData.debugMode) {
\r
4126 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4129 if(move[1]=='*' &&
\r
4130 move[2]>='0' && move[2]<='9' &&
\r
4131 move[3]>='a' && move[3]<='x' ) {
\r
4133 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4134 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4136 if(move[0]>='0' && move[0]<='9' &&
\r
4137 move[1]>='a' && move[1]<='x' &&
\r
4138 move[2]>='0' && move[2]<='9' &&
\r
4139 move[3]>='a' && move[3]<='x' ) {
\r
4140 /* input move, Shogi -> normal */
\r
4141 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4142 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4143 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4144 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4146 if(move[1]=='@' &&
\r
4147 move[3]>='0' && move[3]<='9' &&
\r
4148 move[2]>='a' && move[2]<='x' ) {
\r
4150 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4151 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4154 move[0]>='a' && move[0]<='x' &&
\r
4155 move[3]>='0' && move[3]<='9' &&
\r
4156 move[2]>='a' && move[2]<='x' ) {
\r
4157 /* output move, normal -> Shogi */
\r
4158 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4159 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4160 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4161 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4162 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4164 if (appData.debugMode) {
\r
4165 fprintf(debugFP, " out = '%s'\n", move);
\r
4169 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4171 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4174 ChessMove *moveType;
\r
4175 int *fromX, *fromY, *toX, *toY;
\r
4178 if (appData.debugMode) {
\r
4179 fprintf(debugFP, "move to parse: %s\n", move);
\r
4181 *moveType = yylexstr(moveNum, move);
\r
4183 switch (*moveType) {
\r
4184 case WhitePromotionChancellor:
\r
4185 case BlackPromotionChancellor:
\r
4186 case WhitePromotionArchbishop:
\r
4187 case BlackPromotionArchbishop:
\r
4188 case WhitePromotionQueen:
\r
4189 case BlackPromotionQueen:
\r
4190 case WhitePromotionRook:
\r
4191 case BlackPromotionRook:
\r
4192 case WhitePromotionBishop:
\r
4193 case BlackPromotionBishop:
\r
4194 case WhitePromotionKnight:
\r
4195 case BlackPromotionKnight:
\r
4196 case WhitePromotionKing:
\r
4197 case BlackPromotionKing:
\r
4199 case WhiteCapturesEnPassant:
\r
4200 case BlackCapturesEnPassant:
\r
4201 case WhiteKingSideCastle:
\r
4202 case WhiteQueenSideCastle:
\r
4203 case BlackKingSideCastle:
\r
4204 case BlackQueenSideCastle:
\r
4205 case WhiteKingSideCastleWild:
\r
4206 case WhiteQueenSideCastleWild:
\r
4207 case BlackKingSideCastleWild:
\r
4208 case BlackQueenSideCastleWild:
\r
4209 /* Code added by Tord: */
\r
4210 case WhiteHSideCastleFR:
\r
4211 case WhiteASideCastleFR:
\r
4212 case BlackHSideCastleFR:
\r
4213 case BlackASideCastleFR:
\r
4214 /* End of code added by Tord */
\r
4215 case IllegalMove: /* bug or odd chess variant */
\r
4216 *fromX = currentMoveString[0] - AAA;
\r
4217 *fromY = currentMoveString[1] - ONE;
\r
4218 *toX = currentMoveString[2] - AAA;
\r
4219 *toY = currentMoveString[3] - ONE;
\r
4220 *promoChar = currentMoveString[4];
\r
4221 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4222 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4223 if (appData.debugMode) {
\r
4224 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4226 *fromX = *fromY = *toX = *toY = 0;
\r
4229 if (appData.testLegality) {
\r
4230 return (*moveType != IllegalMove);
\r
4232 return !(fromX == fromY && toX == toY);
\r
4237 *fromX = *moveType == WhiteDrop ?
\r
4238 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4239 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4240 *fromY = DROP_RANK;
\r
4241 *toX = currentMoveString[2] - AAA;
\r
4242 *toY = currentMoveString[3] - ONE;
\r
4243 *promoChar = NULLCHAR;
\r
4246 case AmbiguousMove:
\r
4247 case ImpossibleMove:
\r
4248 case (ChessMove) 0: /* end of file */
\r
4257 if (appData.debugMode) {
\r
4258 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4261 *fromX = *fromY = *toX = *toY = 0;
\r
4262 *promoChar = NULLCHAR;
\r
4267 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
\r
4268 // All positions will have equal probability, but the current method will not provide a unique
\r
4269 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4274 int squaresLeft[4];
\r
4275 int piecesLeft[(int)BlackPawn];
\r
4276 int seed, nrOfShuffles;
\r
4278 void GetPositionNumber()
\r
4279 { // sets global variable seed
\r
4282 seed = appData.defaultFrcPosition;
\r
4283 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4284 for(i=0; i<50; i++) seed += random();
\r
4285 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4286 if(seed<0) seed = -seed;
\r
4290 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4291 // put the piece on the (n-1)-th empty squares of the given shade
\r
4295 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4296 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4297 board[rank][i] = (ChessSquare) pieceType;
\r
4298 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
\r
4299 squaresLeft[ANY]--;
\r
4300 piecesLeft[pieceType]--;
\r
4308 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4309 // calculate where the next piece goes, (any empty square), and put it there
\r
4313 i = seed % squaresLeft[shade];
\r
4314 nrOfShuffles *= squaresLeft[shade];
\r
4315 seed /= squaresLeft[shade];
\r
4316 put(board, pieceType, rank, i, shade);
\r
4319 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4320 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4322 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4324 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4325 i = seed % k; // pick one
\r
4326 nrOfShuffles *= k;
\r
4328 while(i >= j) i -= j--;
\r
4329 j = n - 1 - j; i += j;
\r
4330 put(board, pieceType, rank, j, ANY);
\r
4331 put(board, pieceType, rank, i, ANY);
\r
4334 void SetUpShuffle(Board board, int number)
\r
4336 int i, p, first=1;
\r
4338 GetPositionNumber(); nrOfShuffles = 1;
\r
4340 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4341 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4342 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4344 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4346 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4347 p = (int) board[0][i];
\r
4348 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4349 board[0][i] = EmptySquare;
\r
4352 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4353 // shuffles restricted to allow normal castling put KRR first
\r
4354 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4355 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4356 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4357 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4358 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4359 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4360 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4361 put(board, WhiteRook, 0, 0, ANY);
\r
4362 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4365 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
\r
4366 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4367 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4368 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4369 while(piecesLeft[p] >= 2) {
\r
4370 AddOnePiece(board, p, 0, LITE);
\r
4371 AddOnePiece(board, p, 0, DARK);
\r
4373 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4376 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4377 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4378 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4379 if(p == (int)WhiteRook) continue;
\r
4380 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4381 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4384 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4386 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4387 // Last King gets castling rights
\r
4388 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4389 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4390 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4393 while(piecesLeft[(int)WhiteKing]) {
\r
4394 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4395 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4400 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4401 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4404 // Only Rooks can be left; simply place them all
\r
4405 while(piecesLeft[(int)WhiteRook]) {
\r
4406 i = put(board, WhiteRook, 0, 0, ANY);
\r
4407 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4410 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4412 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4415 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4416 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4419 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4422 int SetCharTable( char *table, const char * map )
\r
4423 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4424 /* Basically a safe strcpy that uses the last character as King */
\r
4426 int result = FALSE; int NrPieces;
\r
4428 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4429 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4430 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4432 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4433 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4434 table[i] = map[i];
\r
4435 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4437 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4438 table[(int) BlackKing] = map[NrPieces-1];
\r
4446 void Prelude(Board board)
\r
4447 { // [HGM] superchess: random selection of exo-pieces
\r
4448 int i, j, k; ChessSquare p;
\r
4449 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4451 GetPositionNumber(); // use FRC position number
\r
4453 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4454 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4455 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4456 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4459 j = seed%4; seed /= 4;
\r
4460 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4461 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4462 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4463 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4464 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4465 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4466 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4467 j = seed%3; seed /= 3;
\r
4468 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4469 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4470 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4471 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4472 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4473 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4474 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4475 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4476 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4477 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4478 put(board, exoPieces[0], 0, 0, ANY);
\r
4479 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4483 InitPosition(redraw)
\r
4486 ChessSquare (* pieces)[BOARD_SIZE];
\r
4487 int i, j, pawnRow, overrule,
\r
4488 oldx = gameInfo.boardWidth,
\r
4489 oldy = gameInfo.boardHeight,
\r
4490 oldh = gameInfo.holdingsWidth,
\r
4491 oldv = gameInfo.variant;
\r
4493 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4494 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4496 /* [AS] Initialize pv info list [HGM] and game status */
\r
4498 for( i=0; i<MAX_MOVES; i++ ) {
\r
4499 pvInfoList[i].depth = 0;
\r
4500 epStatus[i]=EP_NONE;
\r
4501 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4504 initialRulePlies = 0; /* 50-move counter start */
\r
4506 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4507 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4511 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4512 /* the initialized data only consist of the two backranks. The switch */
\r
4513 /* selects which one we will use, which is than copied to the Board */
\r
4514 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4515 /* empty squares. This initial position is then copied to boards[0], */
\r
4516 /* possibly after shuffling, so that it remains available. */
\r
4518 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4519 gameInfo.boardWidth = 8;
\r
4520 gameInfo.boardHeight = 8;
\r
4521 gameInfo.holdingsSize = 0;
\r
4522 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4523 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4524 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4526 switch (gameInfo.variant) {
\r
4527 case VariantFischeRandom:
\r
4528 shuffleOpenings = TRUE;
\r
4530 pieces = FIDEArray;
\r
4532 case VariantShatranj:
\r
4533 pieces = ShatranjArray;
\r
4534 nrCastlingRights = 0;
\r
4535 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4537 case VariantTwoKings:
\r
4538 pieces = twoKingsArray;
\r
4540 case VariantCapaRandom:
\r
4541 shuffleOpenings = TRUE;
\r
4542 case VariantCapablanca:
\r
4543 pieces = CapablancaArray;
\r
4544 gameInfo.boardWidth = 10;
\r
4545 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4547 case VariantGothic:
\r
4548 pieces = GothicArray;
\r
4549 gameInfo.boardWidth = 10;
\r
4550 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4552 case VariantJanus:
\r
4553 pieces = JanusArray;
\r
4554 gameInfo.boardWidth = 10;
\r
4555 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4556 nrCastlingRights = 6;
\r
4557 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4558 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4559 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
\r
4560 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4561 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4562 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
\r
4564 case VariantFalcon:
\r
4565 pieces = FalconArray;
\r
4566 gameInfo.boardWidth = 10;
\r
4567 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4569 case VariantXiangqi:
\r
4570 pieces = XiangqiArray;
\r
4571 gameInfo.boardWidth = 9;
\r
4572 gameInfo.boardHeight = 10;
\r
4573 nrCastlingRights = 0;
\r
4574 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4576 case VariantShogi:
\r
4577 pieces = ShogiArray;
\r
4578 gameInfo.boardWidth = 9;
\r
4579 gameInfo.boardHeight = 9;
\r
4580 gameInfo.holdingsSize = 7;
\r
4581 nrCastlingRights = 0;
\r
4582 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4584 case VariantCourier:
\r
4585 pieces = CourierArray;
\r
4586 gameInfo.boardWidth = 12;
\r
4587 nrCastlingRights = 0;
\r
4588 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4589 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4591 case VariantKnightmate:
\r
4592 pieces = KnightmateArray;
\r
4593 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4595 case VariantFairy:
\r
4596 pieces = fairyArray;
\r
4597 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4599 case VariantGreat:
\r
4600 pieces = GreatArray;
\r
4601 gameInfo.boardWidth = 10;
\r
4602 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4603 gameInfo.holdingsSize = 8;
\r
4605 case VariantSuper:
\r
4606 pieces = FIDEArray;
\r
4607 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4608 gameInfo.holdingsSize = 8;
\r
4609 startedFromSetupPosition = TRUE;
\r
4611 case VariantCrazyhouse:
\r
4612 case VariantBughouse:
\r
4613 pieces = FIDEArray;
\r
4614 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4615 gameInfo.holdingsSize = 5;
\r
4617 case VariantWildCastle:
\r
4618 pieces = FIDEArray;
\r
4619 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4620 shuffleOpenings = 1;
\r
4622 case VariantNoCastle:
\r
4623 pieces = FIDEArray;
\r
4624 nrCastlingRights = 0;
\r
4625 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4626 /* !!?unconstrained back-rank shuffle */
\r
4627 shuffleOpenings = 1;
\r
4632 if(appData.NrFiles >= 0) {
\r
4633 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4634 gameInfo.boardWidth = appData.NrFiles;
\r
4636 if(appData.NrRanks >= 0) {
\r
4637 gameInfo.boardHeight = appData.NrRanks;
\r
4639 if(appData.holdingsSize >= 0) {
\r
4640 i = appData.holdingsSize;
\r
4641 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4642 gameInfo.holdingsSize = i;
\r
4644 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4645 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4646 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4648 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4649 if(pawnRow < 1) pawnRow = 1;
\r
4651 /* User pieceToChar list overrules defaults */
\r
4652 if(appData.pieceToCharTable != NULL)
\r
4653 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4655 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4657 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4658 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4659 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4660 initialPosition[i][j] = s;
\r
4662 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4663 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4664 initialPosition[pawnRow][j] = WhitePawn;
\r
4665 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4666 if(gameInfo.variant == VariantXiangqi) {
\r
4668 initialPosition[pawnRow][j] =
\r
4669 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4670 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4671 initialPosition[2][j] = WhiteCannon;
\r
4672 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4676 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4678 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4681 initialPosition[1][j] = WhiteBishop;
\r
4682 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4684 initialPosition[1][j] = WhiteRook;
\r
4685 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4688 if( nrCastlingRights == -1) {
\r
4689 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4690 /* This sets default castling rights from none to normal corners */
\r
4691 /* Variants with other castling rights must set them themselves above */
\r
4692 nrCastlingRights = 6;
\r
4694 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4695 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4696 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4697 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4698 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4699 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4702 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4703 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4704 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
\r
4705 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
\r
4706 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4707 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4710 if(gameInfo.variant == VariantFischeRandom) {
\r
4711 if( appData.defaultFrcPosition < 0 ) {
\r
4712 ShuffleFRC( initialPosition );
\r
4715 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4717 startedFromSetupPosition = TRUE;
\r
4720 if (appData.debugMode) {
\r
4721 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4723 if(shuffleOpenings) {
\r
4724 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4725 startedFromSetupPosition = TRUE;
\r
4728 if(startedFromPositionFile) {
\r
4729 /* [HGM] loadPos: use PositionFile for every new game */
\r
4730 CopyBoard(initialPosition, filePosition);
\r
4731 for(i=0; i<nrCastlingRights; i++)
\r
4732 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4733 startedFromSetupPosition = TRUE;
\r
4736 CopyBoard(boards[0], initialPosition);
\r
4738 if(oldx != gameInfo.boardWidth ||
\r
4739 oldy != gameInfo.boardHeight ||
\r
4740 oldh != gameInfo.holdingsWidth
\r
4742 || oldv == VariantGothic || // For licensing popups
\r
4743 gameInfo.variant == VariantGothic
\r
4746 || oldv == VariantFalcon ||
\r
4747 gameInfo.variant == VariantFalcon
\r
4750 InitDrawingSizes(-2 ,0);
\r
4753 DrawPosition(TRUE, boards[currentMove]);
\r
4757 SendBoard(cps, moveNum)
\r
4758 ChessProgramState *cps;
\r
4761 char message[MSG_SIZ];
\r
4763 if (cps->useSetboard) {
\r
4764 char* fen = PositionToFEN(moveNum, cps->fenOverride);
\r
4765 sprintf(message, "setboard %s\n", fen);
\r
4766 SendToProgram(message, cps);
\r
4772 /* Kludge to set black to move, avoiding the troublesome and now
\r
4773 * deprecated "black" command.
\r
4775 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4777 SendToProgram("edit\n", cps);
\r
4778 SendToProgram("#\n", cps);
\r
4779 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4780 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4781 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4782 if ((int) *bp < (int) BlackPawn) {
\r
4783 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4784 AAA + j, ONE + i);
\r
4785 if(message[0] == '+' || message[0] == '~') {
\r
4786 sprintf(message, "%c%c%c+\n",
\r
4787 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4788 AAA + j, ONE + i);
\r
4790 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4791 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4792 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4794 SendToProgram(message, cps);
\r
4799 SendToProgram("c\n", cps);
\r
4800 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4801 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4802 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4803 if (((int) *bp != (int) EmptySquare)
\r
4804 && ((int) *bp >= (int) BlackPawn)) {
\r
4805 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4806 AAA + j, ONE + i);
\r
4807 if(message[0] == '+' || message[0] == '~') {
\r
4808 sprintf(message, "%c%c%c+\n",
\r
4809 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4810 AAA + j, ONE + i);
\r
4812 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4813 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4814 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4816 SendToProgram(message, cps);
\r
4821 SendToProgram(".\n", cps);
\r
4823 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4827 IsPromotion(fromX, fromY, toX, toY)
\r
4828 int fromX, fromY, toX, toY;
\r
4830 /* [HGM] add Shogi promotions */
\r
4831 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4832 ChessSquare piece;
\r
4834 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4835 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4836 /* [HGM] Note to self: line above also weeds out drops */
\r
4837 piece = boards[currentMove][fromY][fromX];
\r
4838 if(gameInfo.variant == VariantShogi) {
\r
4839 promotionZoneSize = 3;
\r
4840 highestPromotingPiece = (int)WhiteKing;
\r
4841 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4842 and if in normal chess we then allow promotion to King, why not
\r
4843 allow promotion of other piece in Shogi? */
\r
4845 if((int)piece >= BlackPawn) {
\r
4846 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4848 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4850 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4851 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4853 return ( (int)piece <= highestPromotingPiece );
\r
4857 InPalace(row, column)
\r
4859 { /* [HGM] for Xiangqi */
\r
4860 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4861 column < (BOARD_WIDTH + 4)/2 &&
\r
4862 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4867 PieceForSquare (x, y)
\r
4871 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4874 return boards[currentMove][y][x];
\r
4878 OKToStartUserMove(x, y)
\r
4881 ChessSquare from_piece;
\r
4884 if (matchMode) return FALSE;
\r
4885 if (gameMode == EditPosition) return TRUE;
\r
4887 if (x >= 0 && y >= 0)
\r
4888 from_piece = boards[currentMove][y][x];
\r
4890 from_piece = EmptySquare;
\r
4892 if (from_piece == EmptySquare) return FALSE;
\r
4894 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4895 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4897 switch (gameMode) {
\r
4898 case PlayFromGameFile:
\r
4900 case TwoMachinesPlay:
\r
4904 case IcsObserving:
\r
4908 case MachinePlaysWhite:
\r
4909 case IcsPlayingBlack:
\r
4910 if (appData.zippyPlay) return FALSE;
\r
4911 if (white_piece) {
\r
4912 DisplayMoveError(_("You are playing Black"));
\r
4917 case MachinePlaysBlack:
\r
4918 case IcsPlayingWhite:
\r
4919 if (appData.zippyPlay) return FALSE;
\r
4920 if (!white_piece) {
\r
4921 DisplayMoveError(_("You are playing White"));
\r
4927 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4928 DisplayMoveError(_("It is White's turn"));
\r
4931 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4932 DisplayMoveError(_("It is Black's turn"));
\r
4935 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4936 /* Editing correspondence game history */
\r
4937 /* Could disallow this or prompt for confirmation */
\r
4938 cmailOldMove = -1;
\r
4940 if (currentMove < forwardMostMove) {
\r
4941 /* Discarding moves */
\r
4942 /* Could prompt for confirmation here,
\r
4943 but I don't think that's such a good idea */
\r
4944 forwardMostMove = currentMove;
\r
4948 case BeginningOfGame:
\r
4949 if (appData.icsActive) return FALSE;
\r
4950 if (!appData.noChessProgram) {
\r
4951 if (!white_piece) {
\r
4952 DisplayMoveError(_("You are playing White"));
\r
4959 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4960 DisplayMoveError(_("It is White's turn"));
\r
4963 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4964 DisplayMoveError(_("It is Black's turn"));
\r
4970 case IcsExamining:
\r
4973 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4974 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4975 DisplayMoveError(_("Displayed position is not current"));
\r
4981 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4982 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4983 int lastLoadGameUseList = FALSE;
\r
4984 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4985 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4989 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4990 int fromX, fromY, toX, toY;
\r
4993 ChessMove moveType;
\r
4994 ChessSquare pdown, pup;
\r
4996 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4997 if ((fromX == toX) && (fromY == toY)) {
\r
4998 return ImpossibleMove;
\r
5001 /* [HGM] suppress all moves into holdings area and guard band */
\r
5002 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
5003 return ImpossibleMove;
\r
5005 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5006 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5007 pdown = boards[currentMove][fromY][fromX];
\r
5008 pup = boards[currentMove][toY][toX];
\r
5009 if ( gameMode != EditPosition &&
\r
5010 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5011 WhitePawn <= pup && pup < BlackPawn ||
\r
5012 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5013 BlackPawn <= pup && pup < EmptySquare
\r
5014 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5015 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5016 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5018 return ImpossibleMove;
\r
5020 /* Check if the user is playing in turn. This is complicated because we
\r
5021 let the user "pick up" a piece before it is his turn. So the piece he
\r
5022 tried to pick up may have been captured by the time he puts it down!
\r
5023 Therefore we use the color the user is supposed to be playing in this
\r
5024 test, not the color of the piece that is currently on the starting
\r
5025 square---except in EditGame mode, where the user is playing both
\r
5026 sides; fortunately there the capture race can't happen. (It can
\r
5027 now happen in IcsExamining mode, but that's just too bad. The user
\r
5028 will get a somewhat confusing message in that case.)
\r
5031 switch (gameMode) {
\r
5032 case PlayFromGameFile:
\r
5034 case TwoMachinesPlay:
\r
5036 case IcsObserving:
\r
5038 /* We switched into a game mode where moves are not accepted,
\r
5039 perhaps while the mouse button was down. */
\r
5040 return ImpossibleMove;
\r
5042 case MachinePlaysWhite:
\r
5043 /* User is moving for Black */
\r
5044 if (WhiteOnMove(currentMove)) {
\r
5045 DisplayMoveError(_("It is White's turn"));
\r
5046 return ImpossibleMove;
\r
5050 case MachinePlaysBlack:
\r
5051 /* User is moving for White */
\r
5052 if (!WhiteOnMove(currentMove)) {
\r
5053 DisplayMoveError(_("It is Black's turn"));
\r
5054 return ImpossibleMove;
\r
5059 case IcsExamining:
\r
5060 case BeginningOfGame:
\r
5063 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5064 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5065 /* User is moving for Black */
\r
5066 if (WhiteOnMove(currentMove)) {
\r
5067 DisplayMoveError(_("It is White's turn"));
\r
5068 return ImpossibleMove;
\r
5071 /* User is moving for White */
\r
5072 if (!WhiteOnMove(currentMove)) {
\r
5073 DisplayMoveError(_("It is Black's turn"));
\r
5074 return ImpossibleMove;
\r
5079 case IcsPlayingBlack:
\r
5080 /* User is moving for Black */
\r
5081 if (WhiteOnMove(currentMove)) {
\r
5082 if (!appData.premove) {
\r
5083 DisplayMoveError(_("It is White's turn"));
\r
5084 } else if (toX >= 0 && toY >= 0) {
\r
5087 premoveFromX = fromX;
\r
5088 premoveFromY = fromY;
\r
5089 premovePromoChar = promoChar;
\r
5091 if (appData.debugMode)
\r
5092 fprintf(debugFP, "Got premove: fromX %d,"
\r
5093 "fromY %d, toX %d, toY %d\n",
\r
5094 fromX, fromY, toX, toY);
\r
5096 return ImpossibleMove;
\r
5100 case IcsPlayingWhite:
\r
5101 /* User is moving for White */
\r
5102 if (!WhiteOnMove(currentMove)) {
\r
5103 if (!appData.premove) {
\r
5104 DisplayMoveError(_("It is Black's turn"));
\r
5105 } else if (toX >= 0 && toY >= 0) {
\r
5108 premoveFromX = fromX;
\r
5109 premoveFromY = fromY;
\r
5110 premovePromoChar = promoChar;
\r
5112 if (appData.debugMode)
\r
5113 fprintf(debugFP, "Got premove: fromX %d,"
\r
5114 "fromY %d, toX %d, toY %d\n",
\r
5115 fromX, fromY, toX, toY);
\r
5117 return ImpossibleMove;
\r
5124 case EditPosition:
\r
5125 /* EditPosition, empty square, or different color piece;
\r
5126 click-click move is possible */
\r
5127 if (toX == -2 || toY == -2) {
\r
5128 boards[0][fromY][fromX] = EmptySquare;
\r
5129 return AmbiguousMove;
\r
5130 } else if (toX >= 0 && toY >= 0) {
\r
5131 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5132 boards[0][fromY][fromX] = EmptySquare;
\r
5133 return AmbiguousMove;
\r
5135 return ImpossibleMove;
\r
5138 /* [HGM] If move started in holdings, it means a drop */
\r
5139 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5140 if( pup != EmptySquare ) return ImpossibleMove;
\r
5141 if(appData.testLegality) {
\r
5142 /* it would be more logical if LegalityTest() also figured out
\r
5143 * which drops are legal. For now we forbid pawns on back rank.
\r
5144 * Shogi is on its own here...
\r
5146 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5147 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5148 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5150 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5153 userOfferedDraw = FALSE;
\r
5155 /* [HGM] always test for legality, to get promotion info */
\r
5156 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5157 epStatus[currentMove], castlingRights[currentMove],
\r
5158 fromY, fromX, toY, toX, promoChar);
\r
5160 /* [HGM] but possibly ignore an IllegalMove result */
\r
5161 if (appData.testLegality) {
\r
5162 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5163 DisplayMoveError(_("Illegal move"));
\r
5164 return ImpossibleMove;
\r
5167 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5169 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5170 function is made into one that returns an OK move type if FinishMove
\r
5171 should be called. This to give the calling driver routine the
\r
5172 opportunity to finish the userMove input with a promotion popup,
\r
5173 without bothering the user with this for invalid or illegal moves */
\r
5175 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5178 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5180 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5181 ChessMove moveType;
\r
5182 int fromX, fromY, toX, toY;
\r
5183 /*char*/int promoChar;
\r
5185 char *bookHit = 0;
\r
5186 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5187 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5188 // [HGM] superchess: suppress promotions to non-available piece
\r
5189 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5190 if(WhiteOnMove(currentMove)) {
\r
5191 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5193 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5197 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5198 move type in caller when we know the move is a legal promotion */
\r
5199 if(moveType == NormalMove && promoChar)
\r
5200 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5201 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5202 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5203 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5204 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5205 fromX = boards[currentMove][fromY][fromX];
\r
5206 fromY = DROP_RANK;
\r
5209 /* [HGM] <popupFix> The following if has been moved here from
\r
5210 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5211 piece drops in training games?), and because it can only be
\r
5212 performed after it is known to what we promote. */
\r
5213 if (gameMode == Training) {
\r
5214 /* compare the move played on the board to the next move in the
\r
5215 * game. If they match, display the move and the opponent's response.
\r
5216 * If they don't match, display an error message.
\r
5220 CopyBoard(testBoard, boards[currentMove]);
\r
5221 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5223 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5224 ForwardInner(currentMove+1);
\r
5226 /* Autoplay the opponent's response.
\r
5227 * if appData.animate was TRUE when Training mode was entered,
\r
5228 * the response will be animated.
\r
5230 saveAnimate = appData.animate;
\r
5231 appData.animate = animateTraining;
\r
5232 ForwardInner(currentMove+1);
\r
5233 appData.animate = saveAnimate;
\r
5235 /* check for the end of the game */
\r
5236 if (currentMove >= forwardMostMove) {
\r
5237 gameMode = PlayFromGameFile;
\r
5239 SetTrainingModeOff();
\r
5240 DisplayInformation(_("End of game"));
\r
5243 DisplayError(_("Incorrect move"), 0);
\r
5248 /* Ok, now we know that the move is good, so we can kill
\r
5249 the previous line in Analysis Mode */
\r
5250 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5251 forwardMostMove = currentMove;
\r
5254 /* If we need the chess program but it's dead, restart it */
\r
5255 ResurrectChessProgram();
\r
5257 /* A user move restarts a paused game*/
\r
5261 thinkOutput[0] = NULLCHAR;
\r
5263 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5265 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5266 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5267 // [HGM] superchess: take promotion piece out of holdings
\r
5268 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5269 if(WhiteOnMove(forwardMostMove-1)) {
\r
5270 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5271 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5273 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5274 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5278 if (gameMode == BeginningOfGame) {
\r
5279 if (appData.noChessProgram) {
\r
5280 gameMode = EditGame;
\r
5283 char buf[MSG_SIZ];
\r
5284 gameMode = MachinePlaysBlack;
\r
5287 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5288 DisplayTitle(buf);
\r
5289 if (first.sendName) {
\r
5290 sprintf(buf, "name %s\n", gameInfo.white);
\r
5291 SendToProgram(buf, &first);
\r
5297 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5298 /* Relay move to ICS or chess engine */
\r
5299 if (appData.icsActive) {
\r
5300 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5301 gameMode == IcsExamining) {
\r
5302 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5303 ics_user_moved = 1;
\r
5306 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5307 gameMode == MachinePlaysWhite ||
\r
5308 gameMode == MachinePlaysBlack)) {
\r
5309 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5311 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5312 // [HGM] book: if program might be playing, let it use book
\r
5313 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5314 first.maybeThinking = TRUE;
\r
5315 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5316 if (currentMove == cmailOldMove + 1) {
\r
5317 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5321 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5323 switch (gameMode) {
\r
5325 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5326 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5330 case MT_CHECKMATE:
\r
5331 if (WhiteOnMove(currentMove)) {
\r
5332 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5334 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5337 case MT_STALEMATE:
\r
5338 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5343 case MachinePlaysBlack:
\r
5344 case MachinePlaysWhite:
\r
5345 /* disable certain menu options while machine is thinking */
\r
5346 SetMachineThinkingEnables();
\r
5353 if(bookHit) { // [HGM] book: simulate book reply
\r
5354 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5356 programStats.nodes = programStats.depth = programStats.time =
\r
5357 programStats.score = programStats.got_only_move = 0;
\r
5358 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5360 strcpy(bookMove, "move ");
\r
5361 strcat(bookMove, bookHit);
\r
5362 HandleMachineMove(bookMove, &first);
\r
5368 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5369 int fromX, fromY, toX, toY;
\r
5372 /* [HGM] This routine was added to allow calling of its two logical
\r
5373 parts from other modules in the old way. Before, UserMoveEvent()
\r
5374 automatically called FinishMove() if the move was OK, and returned
\r
5375 otherwise. I separated the two, in order to make it possible to
\r
5376 slip a promotion popup in between. But that it always needs two
\r
5377 calls, to the first part, (now called UserMoveTest() ), and to
\r
5378 FinishMove if the first part succeeded. Calls that do not need
\r
5379 to do anything in between, can call this routine the old way.
\r
5381 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5382 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5383 if(moveType != ImpossibleMove)
\r
5384 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5389 // char * hint = lastHint;
\r
5390 FrontEndProgramStats stats;
\r
5392 stats.which = cps == &first ? 0 : 1;
\r
5393 stats.depth = cpstats->depth;
\r
5394 stats.nodes = cpstats->nodes;
\r
5395 stats.score = cpstats->score;
\r
5396 stats.time = cpstats->time;
\r
5397 stats.pv = cpstats->movelist;
\r
5398 stats.hint = lastHint;
\r
5399 stats.an_move_index = 0;
\r
5400 stats.an_move_count = 0;
\r
5402 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5403 stats.hint = cpstats->move_name;
\r
5404 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5405 stats.an_move_count = cpstats->nr_moves;
\r
5408 SetProgramStats( &stats );
\r
5411 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5412 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5413 char *bookHit = NULL;
\r
5415 //first determine if the incoming move brings opponent into his book
\r
5416 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5417 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5418 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5419 if(bookHit != NULL && !cps->bookSuspend) {
\r
5420 // make sure opponent is not going to reply after receiving move to book position
\r
5421 SendToProgram("force\n", cps);
\r
5422 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5424 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5425 // now arrange restart after book miss
\r
5427 // after a book hit we never send 'go', and the code after the call to this routine
\r
5428 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5429 char buf[MSG_SIZ];
\r
5430 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5431 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5432 SendToProgram(buf, cps);
\r
5433 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5434 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5435 SendToProgram("go\n", cps);
\r
5436 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5437 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5438 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5439 SendToProgram("go\n", cps);
\r
5440 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5442 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5445 char *savedMessage;
\r
5446 ChessProgramState *savedState;
\r
5447 void DeferredBookMove(void)
\r
5449 if(savedState->lastPing != savedState->lastPong)
\r
5450 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5452 HandleMachineMove(savedMessage, savedState);
\r
5456 HandleMachineMove(message, cps)
\r
5458 ChessProgramState *cps;
\r
5460 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5461 char realname[MSG_SIZ];
\r
5462 int fromX, fromY, toX, toY;
\r
5463 ChessMove moveType;
\r
5469 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5471 * Kludge to ignore BEL characters
\r
5473 while (*message == '\007') message++;
\r
5476 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5478 if(cps->debug && *message == '#') return;
\r
5481 * Look for book output
\r
5483 if (cps == &first && bookRequested) {
\r
5484 if (message[0] == '\t' || message[0] == ' ') {
\r
5485 /* Part of the book output is here; append it */
\r
5486 strcat(bookOutput, message);
\r
5487 strcat(bookOutput, " \n");
\r
5489 } else if (bookOutput[0] != NULLCHAR) {
\r
5490 /* All of book output has arrived; display it */
\r
5491 char *p = bookOutput;
\r
5492 while (*p != NULLCHAR) {
\r
5493 if (*p == '\t') *p = ' ';
\r
5496 DisplayInformation(bookOutput);
\r
5497 bookRequested = FALSE;
\r
5498 /* Fall through to parse the current output */
\r
5503 * Look for machine move.
\r
5505 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5506 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5508 /* This method is only useful on engines that support ping */
\r
5509 if (cps->lastPing != cps->lastPong) {
\r
5510 if (gameMode == BeginningOfGame) {
\r
5511 /* Extra move from before last new; ignore */
\r
5512 if (appData.debugMode) {
\r
5513 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5516 if (appData.debugMode) {
\r
5517 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5518 cps->which, gameMode);
\r
5521 SendToProgram("undo\n", cps);
\r
5526 switch (gameMode) {
\r
5527 case BeginningOfGame:
\r
5528 /* Extra move from before last reset; ignore */
\r
5529 if (appData.debugMode) {
\r
5530 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5537 /* Extra move after we tried to stop. The mode test is
\r
5538 not a reliable way of detecting this problem, but it's
\r
5539 the best we can do on engines that don't support ping.
\r
5541 if (appData.debugMode) {
\r
5542 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5543 cps->which, gameMode);
\r
5545 SendToProgram("undo\n", cps);
\r
5548 case MachinePlaysWhite:
\r
5549 case IcsPlayingWhite:
\r
5550 machineWhite = TRUE;
\r
5553 case MachinePlaysBlack:
\r
5554 case IcsPlayingBlack:
\r
5555 machineWhite = FALSE;
\r
5558 case TwoMachinesPlay:
\r
5559 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5562 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5563 if (appData.debugMode) {
\r
5565 "Ignoring move out of turn by %s, gameMode %d"
\r
5566 ", forwardMost %d\n",
\r
5567 cps->which, gameMode, forwardMostMove);
\r
5572 if (appData.debugMode) { int f = forwardMostMove;
\r
5573 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5574 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5576 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5577 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5578 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5579 /* Machine move could not be parsed; ignore it. */
\r
5580 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5581 machineMove, cps->which);
\r
5582 DisplayError(buf1, 0);
\r
5583 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5584 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5585 if (gameMode == TwoMachinesPlay) {
\r
5586 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5592 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5593 /* So we have to redo legality test with true e.p. status here, */
\r
5594 /* to make sure an illegal e.p. capture does not slip through, */
\r
5595 /* to cause a forfeit on a justified illegal-move complaint */
\r
5596 /* of the opponent. */
\r
5597 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5598 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5600 ChessMove moveType;
\r
5601 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5602 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5603 fromY, fromX, toY, toX, promoChar);
\r
5604 if (appData.debugMode) {
\r
5606 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5607 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5608 fprintf(debugFP, "castling rights\n");
\r
5610 if(moveType == IllegalMove) {
\r
5611 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5612 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5613 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5616 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5617 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5618 when they shouldn't (like TSCP-Gothic) */
\r
5619 switch(moveType) {
\r
5620 case WhiteASideCastleFR:
\r
5621 case BlackASideCastleFR:
\r
5623 currentMoveString[2]++;
\r
5625 case WhiteHSideCastleFR:
\r
5626 case BlackHSideCastleFR:
\r
5628 currentMoveString[2]--;
\r
5630 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5633 hintRequested = FALSE;
\r
5634 lastHint[0] = NULLCHAR;
\r
5635 bookRequested = FALSE;
\r
5636 /* Program may be pondering now */
\r
5637 cps->maybeThinking = TRUE;
\r
5638 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5639 if (cps->offeredDraw) cps->offeredDraw--;
\r
5642 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5644 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5645 ics_user_moved = 1;
\r
5646 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5647 char buf[3*MSG_SIZ];
\r
5649 sprintf(buf, "kibitz %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
\r
5650 programStats.score / 100.,
\r
5651 programStats.depth,
\r
5652 programStats.time / 100.,
\r
5653 u64ToDouble(programStats.nodes),
\r
5654 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5655 programStats.movelist);
\r
5660 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5661 strcpy(machineMove, currentMoveString);
\r
5662 strcat(machineMove, "\n");
\r
5663 strcpy(moveList[forwardMostMove], machineMove);
\r
5665 /* [AS] Save move info and clear stats for next move */
\r
5666 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5667 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5668 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5669 ClearProgramStats();
\r
5670 thinkOutput[0] = NULLCHAR;
\r
5671 hiddenThinkOutputState = 0;
\r
5673 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5675 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5676 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5679 while( count < adjudicateLossPlies ) {
\r
5680 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5683 score = -score; /* Flip score for winning side */
\r
5686 if( score > adjudicateLossThreshold ) {
\r
5693 if( count >= adjudicateLossPlies ) {
\r
5694 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5696 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5697 "Xboard adjudication",
\r
5704 if( gameMode == TwoMachinesPlay ) {
\r
5705 // [HGM] some adjudications useful with buggy engines
\r
5706 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5707 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5710 if( appData.testLegality )
\r
5711 { /* [HGM] Some more adjudications for obstinate engines */
\r
5712 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5713 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
\r
5714 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5715 static int moveCount = 6;
\r
5717 /* Count what is on board. */
\r
5718 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5719 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5723 { /* count B,N,R and other of each side */
\r
5726 NrK++; break; // [HGM] atomic: count Kings
\r
5730 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5731 bishopsColor |= 1 << ((i^j)&1);
\r
5736 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5737 bishopsColor |= 1 << ((i^j)&1);
\r
5747 case EmptySquare:
\r
5752 PawnAdvance += m; NrPawns++;
\r
5754 NrPieces += (p != EmptySquare);
\r
5755 NrW += ((int)p < (int)BlackPawn);
\r
5756 if(gameInfo.variant == VariantXiangqi &&
\r
5757 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5758 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5759 NrW -= ((int)p < (int)BlackPawn);
\r
5763 /* Some material-based adjudications that have to be made before stalemate test */
\r
5764 if(gameInfo.variant == VariantAtomic && NrK < 2) {
\r
5765 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
\r
5766 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
\r
5767 if(appData.checkMates) {
\r
5768 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5769 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5770 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5771 "Xboard adjudication: King destroyed", GE_XBOARD );
\r
5776 /* Bare King in Shatranj (loses) or Losers (wins) */
\r
5777 if( NrW == 1 || NrPieces - NrW == 1) {
\r
5778 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
\r
5779 epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win
\r
5780 if(appData.checkMates) {
\r
5781 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5782 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5783 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5784 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5788 if( gameInfo.variant == VariantShatranj && --bare < 0)
\r
5790 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm
\r
5791 if(appData.checkMates) {
\r
5792 /* but only adjudicate if adjudication enabled */
\r
5793 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5794 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5795 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5796 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5803 // don't wait for engine to announce game end if we can judge ourselves
\r
5804 switch (MateTest(boards[forwardMostMove],
\r
5805 PosFlags(forwardMostMove), epFile,
\r
5806 castlingRights[forwardMostMove]) ) {
\r
5811 case MT_STALEMATE:
\r
5812 if(epStatus[forwardMostMove] != EP_CHECKMATE) // [HGM] spare win through baring or K-capt
\r
5813 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5814 if(appData.checkMates) {
\r
5815 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5816 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5817 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide
\r
5818 || gameInfo.variant == VariantGiveaway) // [HGM] losers:
\r
5819 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!
\r
5820 "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5822 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5826 case MT_CHECKMATE:
\r
5827 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5828 if(appData.checkMates) {
\r
5829 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5830 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5831 GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:
\r
5832 ? BlackWins : WhiteWins, // reverse the result ( A!=1 is !A for a boolean)
\r
5833 "Xboard adjudication: Checkmate", GE_XBOARD );
\r
5839 /* Next absolutely insufficient mating material. */
\r
5840 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5841 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5842 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5843 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5844 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5846 /* always flag draws, for judging claims */
\r
5847 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5849 if(appData.materialDraws) {
\r
5850 /* but only adjudicate them if adjudication enabled */
\r
5851 SendToProgram("force\n", cps->other); // suppress reply
\r
5852 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5853 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5854 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5859 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5860 if(NrPieces == 4 &&
\r
5861 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5862 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5863 || NrWN==2 || NrBN==2 /* KNNK */
\r
5864 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5866 if(--moveCount < 0 && appData.trivialDraws)
\r
5867 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5868 SendToProgram("force\n", cps->other); // suppress reply
\r
5869 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5870 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5871 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5874 } else moveCount = 6;
\r
5878 if (appData.debugMode) { int i;
\r
5879 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5880 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5881 appData.drawRepeats);
\r
5882 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5883 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5887 /* Check for rep-draws */
\r
5889 for(k = forwardMostMove-2;
\r
5890 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5891 epStatus[k] < EP_UNKNOWN &&
\r
5892 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5896 if (appData.debugMode) {
\r
5897 fprintf(debugFP, " loop\n");
\r
5900 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5902 if (appData.debugMode) {
\r
5903 fprintf(debugFP, "match\n");
\r
5906 /* compare castling rights */
\r
5907 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5908 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5909 rights++; /* King lost rights, while rook still had them */
\r
5910 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5911 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5912 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5913 rights++; /* but at least one rook lost them */
\r
5915 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5916 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5918 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5919 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5920 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5924 if (appData.debugMode) {
\r
5925 for(i=0; i<nrCastlingRights; i++)
\r
5926 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5929 if (appData.debugMode) {
\r
5930 fprintf(debugFP, " %d %d\n", rights, k);
\r
5933 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5934 && appData.drawRepeats > 1) {
\r
5935 /* adjudicate after user-specified nr of repeats */
\r
5936 SendToProgram("force\n", cps->other); // suppress reply
\r
5937 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5938 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5939 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5940 // [HGM] xiangqi: check for forbidden perpetuals
\r
5941 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5942 for(m=forwardMostMove; m>k; m-=2) {
\r
5943 if(MateTest(boards[m], PosFlags(m),
\r
5944 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5945 ourPerpetual = 0; // the current mover did not always check
\r
5946 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5947 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5948 hisPerpetual = 0; // the opponent did not always check
\r
5950 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
5951 ourPerpetual, hisPerpetual);
\r
5952 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5953 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5954 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
5957 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
5958 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
5959 // Now check for perpetual chases
\r
5960 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
5961 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
5962 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
5963 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5964 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5965 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
5968 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
5969 break; // Abort repetition-checking loop.
\r
5971 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
5973 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5976 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5977 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5981 /* Now we test for 50-move draws. Determine ply count */
\r
5982 count = forwardMostMove;
\r
5983 /* look for last irreversble move */
\r
5984 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5986 /* if we hit starting position, add initial plies */
\r
5987 if( count == backwardMostMove )
\r
5988 count -= initialRulePlies;
\r
5989 count = forwardMostMove - count;
\r
5991 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5992 /* this is used to judge if draw claims are legal */
\r
5993 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5994 SendToProgram("force\n", cps->other); // suppress reply
\r
5995 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5996 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5997 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6001 /* if draw offer is pending, treat it as a draw claim
\r
6002 * when draw condition present, to allow engines a way to
\r
6003 * claim draws before making their move to avoid a race
\r
6004 * condition occurring after their move
\r
6006 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6008 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6009 p = "Draw claim: 50-move rule";
\r
6010 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6011 p = "Draw claim: 3-fold repetition";
\r
6012 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6013 p = "Draw claim: insufficient mating material";
\r
6015 SendToProgram("force\n", cps->other); // suppress reply
\r
6016 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6017 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6018 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6024 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6025 SendToProgram("force\n", cps->other); // suppress reply
\r
6026 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6027 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6029 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6036 if (gameMode == TwoMachinesPlay) {
\r
6037 /* [HGM] relaying draw offers moved to after reception of move */
\r
6038 /* and interpreting offer as claim if it brings draw condition */
\r
6039 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6040 SendToProgram("draw\n", cps->other);
\r
6042 if (cps->other->sendTime) {
\r
6043 SendTimeRemaining(cps->other,
\r
6044 cps->other->twoMachinesColor[0] == 'w');
\r
6046 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6047 if (firstMove && !bookHit) {
\r
6048 firstMove = FALSE;
\r
6049 if (cps->other->useColors) {
\r
6050 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6052 SendToProgram("go\n", cps->other);
\r
6054 cps->other->maybeThinking = TRUE;
\r
6057 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6059 if (!pausing && appData.ringBellAfterMoves) {
\r
6064 * Reenable menu items that were disabled while
\r
6065 * machine was thinking
\r
6067 if (gameMode != TwoMachinesPlay)
\r
6068 SetUserThinkingEnables();
\r
6070 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6071 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6072 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6074 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6076 strcpy(bookMove, "move ");
\r
6077 strcat(bookMove, bookHit);
\r
6078 message = bookMove;
\r
6080 programStats.nodes = programStats.depth = programStats.time =
\r
6081 programStats.score = programStats.got_only_move = 0;
\r
6082 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6084 if(cps->lastPing != cps->lastPong) {
\r
6085 savedMessage = message; // args for deferred call
\r
6087 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6090 goto FakeBookMove;
\r
6096 /* Set special modes for chess engines. Later something general
\r
6097 * could be added here; for now there is just one kludge feature,
\r
6098 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6099 * when "xboard" is given as an interactive command.
\r
6101 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6102 cps->useSigint = FALSE;
\r
6103 cps->useSigterm = FALSE;
\r
6106 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6107 * want this, I was asked to put it in, and obliged.
\r
6109 if (!strncmp(message, "setboard ", 9)) {
\r
6110 Board initial_position; int i;
\r
6112 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6114 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6115 DisplayError(_("Bad FEN received from engine"), 0);
\r
6118 Reset(FALSE, FALSE);
\r
6119 CopyBoard(boards[0], initial_position);
\r
6120 initialRulePlies = FENrulePlies;
\r
6121 epStatus[0] = FENepStatus;
\r
6122 for( i=0; i<nrCastlingRights; i++ )
\r
6123 castlingRights[0][i] = FENcastlingRights[i];
\r
6124 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6125 else gameMode = MachinePlaysBlack;
\r
6126 DrawPosition(FALSE, boards[currentMove]);
\r
6132 * Look for communication commands
\r
6134 if (!strncmp(message, "telluser ", 9)) {
\r
6135 DisplayNote(message + 9);
\r
6138 if (!strncmp(message, "tellusererror ", 14)) {
\r
6139 DisplayError(message + 14, 0);
\r
6142 if (!strncmp(message, "tellopponent ", 13)) {
\r
6143 if (appData.icsActive) {
\r
6145 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6149 DisplayNote(message + 13);
\r
6153 if (!strncmp(message, "tellothers ", 11)) {
\r
6154 if (appData.icsActive) {
\r
6156 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6162 if (!strncmp(message, "tellall ", 8)) {
\r
6163 if (appData.icsActive) {
\r
6165 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6169 DisplayNote(message + 8);
\r
6173 if (strncmp(message, "warning", 7) == 0) {
\r
6174 /* Undocumented feature, use tellusererror in new code */
\r
6175 DisplayError(message, 0);
\r
6178 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6179 strcpy(realname, cps->tidy);
\r
6180 strcat(realname, " query");
\r
6181 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6184 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6185 * sent until we are logged on. Crafty kibitzes have been known to
\r
6186 * interfere with the login process.
\r
6189 if (!strncmp(message, "tellics ", 8)) {
\r
6190 SendToICS(message + 8);
\r
6194 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6195 SendToICS(ics_prefix);
\r
6196 SendToICS(message + 15);
\r
6200 /* The following are for backward compatibility only */
\r
6201 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6202 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6203 SendToICS(ics_prefix);
\r
6204 SendToICS(message);
\r
6209 if (strncmp(message, "feature ", 8) == 0) {
\r
6210 ParseFeatures(message+8, cps);
\r
6212 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6216 * If the move is illegal, cancel it and redraw the board.
\r
6217 * Also deal with other error cases. Matching is rather loose
\r
6218 * here to accommodate engines written before the spec.
\r
6220 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6221 strncmp(message, "Error", 5) == 0) {
\r
6222 if (StrStr(message, "name") ||
\r
6223 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6224 StrStr(message, "result") || StrStr(message, "board") ||
\r
6225 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6226 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6227 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6228 StrStr(message, "accepted")) {
\r
6231 if (StrStr(message, "protover")) {
\r
6232 /* Program is responding to input, so it's apparently done
\r
6233 initializing, and this error message indicates it is
\r
6234 protocol version 1. So we don't need to wait any longer
\r
6235 for it to initialize and send feature commands. */
\r
6236 FeatureDone(cps, 1);
\r
6237 cps->protocolVersion = 1;
\r
6240 cps->maybeThinking = FALSE;
\r
6242 if (StrStr(message, "draw")) {
\r
6243 /* Program doesn't have "draw" command */
\r
6244 cps->sendDrawOffers = 0;
\r
6247 if (cps->sendTime != 1 &&
\r
6248 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6249 /* Program apparently doesn't have "time" or "otim" command */
\r
6250 cps->sendTime = 0;
\r
6253 if (StrStr(message, "analyze")) {
\r
6254 cps->analysisSupport = FALSE;
\r
6255 cps->analyzing = FALSE;
\r
6256 Reset(FALSE, TRUE);
\r
6257 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6258 DisplayError(buf2, 0);
\r
6261 if (StrStr(message, "(no matching move)st")) {
\r
6262 /* Special kludge for GNU Chess 4 only */
\r
6263 cps->stKludge = TRUE;
\r
6264 SendTimeControl(cps, movesPerSession, timeControl,
\r
6265 timeIncrement, appData.searchDepth,
\r
6269 if (StrStr(message, "(no matching move)sd")) {
\r
6270 /* Special kludge for GNU Chess 4 only */
\r
6271 cps->sdKludge = TRUE;
\r
6272 SendTimeControl(cps, movesPerSession, timeControl,
\r
6273 timeIncrement, appData.searchDepth,
\r
6277 if (!StrStr(message, "llegal")) {
\r
6280 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6281 gameMode == IcsIdle) return;
\r
6282 if (forwardMostMove <= backwardMostMove) return;
\r
6284 /* Following removed: it caused a bug where a real illegal move
\r
6285 message in analyze mored would be ignored. */
\r
6286 if (cps == &first && programStats.ok_to_send == 0) {
\r
6287 /* Bogus message from Crafty responding to "." This filtering
\r
6288 can miss some of the bad messages, but fortunately the bug
\r
6289 is fixed in current Crafty versions, so it doesn't matter. */
\r
6293 if (pausing) PauseEvent();
\r
6294 if (gameMode == PlayFromGameFile) {
\r
6295 /* Stop reading this game file */
\r
6296 gameMode = EditGame;
\r
6299 currentMove = --forwardMostMove;
\r
6300 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6302 DisplayBothClocks();
\r
6303 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6304 parseList[currentMove], cps->which);
\r
6305 DisplayMoveError(buf1);
\r
6306 DrawPosition(FALSE, boards[currentMove]);
\r
6308 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6309 /* only passes fully legal moves */
\r
6310 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6311 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6312 "False illegal-move claim", GE_XBOARD );
\r
6316 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6317 /* Program has a broken "time" command that
\r
6318 outputs a string not ending in newline.
\r
6320 cps->sendTime = 0;
\r
6324 * If chess program startup fails, exit with an error message.
\r
6325 * Attempts to recover here are futile.
\r
6327 if ((StrStr(message, "unknown host") != NULL)
\r
6328 || (StrStr(message, "No remote directory") != NULL)
\r
6329 || (StrStr(message, "not found") != NULL)
\r
6330 || (StrStr(message, "No such file") != NULL)
\r
6331 || (StrStr(message, "can't alloc") != NULL)
\r
6332 || (StrStr(message, "Permission denied") != NULL)) {
\r
6334 cps->maybeThinking = FALSE;
\r
6335 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6336 cps->which, cps->program, cps->host, message);
\r
6337 RemoveInputSource(cps->isr);
\r
6338 DisplayFatalError(buf1, 0, 1);
\r
6343 * Look for hint output
\r
6345 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6346 if (cps == &first && hintRequested) {
\r
6347 hintRequested = FALSE;
\r
6348 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6349 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6350 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6351 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6352 fromY, fromX, toY, toX, promoChar, buf1);
\r
6353 sprintf(buf2, _("Hint: %s"), buf1);
\r
6354 DisplayInformation(buf2);
\r
6356 /* Hint move could not be parsed!? */
\r
6358 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6359 buf1, cps->which);
\r
6360 DisplayError(buf2, 0);
\r
6363 strcpy(lastHint, buf1);
\r
6369 * Ignore other messages if game is not in progress
\r
6371 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6372 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6375 * look for win, lose, draw, or draw offer
\r
6377 if (strncmp(message, "1-0", 3) == 0) {
\r
6378 char *p, *q, *r = "";
\r
6379 p = strchr(message, '{');
\r
6381 q = strchr(p, '}');
\r
6387 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6389 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6390 char *p, *q, *r = "";
\r
6391 p = strchr(message, '{');
\r
6393 q = strchr(p, '}');
\r
6399 /* Kludge for Arasan 4.1 bug */
\r
6400 if (strcmp(r, "Black resigns") == 0) {
\r
6401 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6404 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6406 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6407 char *p, *q, *r = "";
\r
6408 p = strchr(message, '{');
\r
6410 q = strchr(p, '}');
\r
6417 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6420 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6421 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6423 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6424 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6426 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6427 strncmp(message, "Black matches", 13) == 0 ) {
\r
6428 /* [HGM] ignore GNUShogi noises */
\r
6430 } else if (strncmp(message, "White", 5) == 0 &&
\r
6431 message[5] != '(' &&
\r
6432 StrStr(message, "Black") == NULL) {
\r
6433 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6435 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6436 message[5] != '(') {
\r
6437 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6439 } else if (strcmp(message, "resign") == 0 ||
\r
6440 strcmp(message, "computer resigns") == 0) {
\r
6441 switch (gameMode) {
\r
6442 case MachinePlaysBlack:
\r
6443 case IcsPlayingBlack:
\r
6444 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6446 case MachinePlaysWhite:
\r
6447 case IcsPlayingWhite:
\r
6448 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6450 case TwoMachinesPlay:
\r
6451 if (cps->twoMachinesColor[0] == 'w')
\r
6452 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6454 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6457 /* can't happen */
\r
6461 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6462 switch (gameMode) {
\r
6463 case MachinePlaysBlack:
\r
6464 case IcsPlayingBlack:
\r
6465 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6467 case MachinePlaysWhite:
\r
6468 case IcsPlayingWhite:
\r
6469 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6471 case TwoMachinesPlay:
\r
6472 if (cps->twoMachinesColor[0] == 'w')
\r
6473 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6475 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6478 /* can't happen */
\r
6482 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6483 switch (gameMode) {
\r
6484 case MachinePlaysBlack:
\r
6485 case IcsPlayingBlack:
\r
6486 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6488 case MachinePlaysWhite:
\r
6489 case IcsPlayingWhite:
\r
6490 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6492 case TwoMachinesPlay:
\r
6493 if (cps->twoMachinesColor[0] == 'w')
\r
6494 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6496 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6499 /* can't happen */
\r
6503 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6504 if (WhiteOnMove(forwardMostMove)) {
\r
6505 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6507 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6510 } else if (strstr(message, "Draw") != NULL ||
\r
6511 strstr(message, "game is a draw") != NULL) {
\r
6512 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6514 } else if (strstr(message, "offer") != NULL &&
\r
6515 strstr(message, "draw") != NULL) {
\r
6517 if (appData.zippyPlay && first.initDone) {
\r
6518 /* Relay offer to ICS */
\r
6519 SendToICS(ics_prefix);
\r
6520 SendToICS("draw\n");
\r
6523 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6524 if (gameMode == TwoMachinesPlay) {
\r
6525 if (cps->other->offeredDraw) {
\r
6526 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6527 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6528 /* until after we also have move, to see if it is really claim */
\r
6532 if (cps->other->sendDrawOffers) {
\r
6533 SendToProgram("draw\n", cps->other);
\r
6537 } else if (gameMode == MachinePlaysWhite ||
\r
6538 gameMode == MachinePlaysBlack) {
\r
6539 if (userOfferedDraw) {
\r
6540 DisplayInformation(_("Machine accepts your draw offer"));
\r
6541 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6543 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6550 * Look for thinking output
\r
6552 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6553 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6555 int plylev, mvleft, mvtot, curscore, time;
\r
6556 char mvname[MOVE_LEN];
\r
6557 u64 nodes; // [DM]
\r
6559 int ignore = FALSE;
\r
6560 int prefixHint = FALSE;
\r
6561 mvname[0] = NULLCHAR;
\r
6563 switch (gameMode) {
\r
6564 case MachinePlaysBlack:
\r
6565 case IcsPlayingBlack:
\r
6566 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6568 case MachinePlaysWhite:
\r
6569 case IcsPlayingWhite:
\r
6570 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6575 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6576 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6578 case TwoMachinesPlay:
\r
6579 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6589 buf1[0] = NULLCHAR;
\r
6590 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6591 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6593 if (plyext != ' ' && plyext != '\t') {
\r
6597 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6598 if( cps->scoreIsAbsolute &&
\r
6599 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6601 curscore = -curscore;
\r
6605 programStats.depth = plylev;
\r
6606 programStats.nodes = nodes;
\r
6607 programStats.time = time;
\r
6608 programStats.score = curscore;
\r
6609 programStats.got_only_move = 0;
\r
6611 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6614 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6615 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6616 if(WhiteOnMove(forwardMostMove))
\r
6617 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6618 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6621 /* Buffer overflow protection */
\r
6622 if (buf1[0] != NULLCHAR) {
\r
6623 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6624 && appData.debugMode) {
\r
6626 "PV is too long; using the first %d bytes.\n",
\r
6627 sizeof(programStats.movelist) - 1);
\r
6630 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6632 sprintf(programStats.movelist, " no PV\n");
\r
6635 if (programStats.seen_stat) {
\r
6636 programStats.ok_to_send = 1;
\r
6639 if (strchr(programStats.movelist, '(') != NULL) {
\r
6640 programStats.line_is_book = 1;
\r
6641 programStats.nr_moves = 0;
\r
6642 programStats.moves_left = 0;
\r
6644 programStats.line_is_book = 0;
\r
6647 SendProgramStatsToFrontend( cps, &programStats );
\r
6650 [AS] Protect the thinkOutput buffer from overflow... this
\r
6651 is only useful if buf1 hasn't overflowed first!
\r
6653 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6655 (gameMode == TwoMachinesPlay ?
\r
6656 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6657 ((double) curscore) / 100.0,
\r
6658 prefixHint ? lastHint : "",
\r
6659 prefixHint ? " " : "" );
\r
6661 if( buf1[0] != NULLCHAR ) {
\r
6662 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6664 if( strlen(buf1) > max_len ) {
\r
6665 if( appData.debugMode) {
\r
6666 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6668 buf1[max_len+1] = '\0';
\r
6671 strcat( thinkOutput, buf1 );
\r
6674 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6675 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6676 DisplayMove(currentMove - 1);
\r
6677 DisplayAnalysis();
\r
6681 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6682 /* crafty (9.25+) says "(only move) <move>"
\r
6683 * if there is only 1 legal move
\r
6685 sscanf(p, "(only move) %s", buf1);
\r
6686 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6687 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6688 programStats.depth = 1;
\r
6689 programStats.nr_moves = 1;
\r
6690 programStats.moves_left = 1;
\r
6691 programStats.nodes = 1;
\r
6692 programStats.time = 1;
\r
6693 programStats.got_only_move = 1;
\r
6695 /* Not really, but we also use this member to
\r
6696 mean "line isn't going to change" (Crafty
\r
6697 isn't searching, so stats won't change) */
\r
6698 programStats.line_is_book = 1;
\r
6700 SendProgramStatsToFrontend( cps, &programStats );
\r
6702 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6703 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6704 DisplayMove(currentMove - 1);
\r
6705 DisplayAnalysis();
\r
6708 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6709 &time, &nodes, &plylev, &mvleft,
\r
6710 &mvtot, mvname) >= 5) {
\r
6711 /* The stat01: line is from Crafty (9.29+) in response
\r
6712 to the "." command */
\r
6713 programStats.seen_stat = 1;
\r
6714 cps->maybeThinking = TRUE;
\r
6716 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6719 programStats.depth = plylev;
\r
6720 programStats.time = time;
\r
6721 programStats.nodes = nodes;
\r
6722 programStats.moves_left = mvleft;
\r
6723 programStats.nr_moves = mvtot;
\r
6724 strcpy(programStats.move_name, mvname);
\r
6725 programStats.ok_to_send = 1;
\r
6726 programStats.movelist[0] = '\0';
\r
6728 SendProgramStatsToFrontend( cps, &programStats );
\r
6730 DisplayAnalysis();
\r
6733 } else if (strncmp(message,"++",2) == 0) {
\r
6734 /* Crafty 9.29+ outputs this */
\r
6735 programStats.got_fail = 2;
\r
6738 } else if (strncmp(message,"--",2) == 0) {
\r
6739 /* Crafty 9.29+ outputs this */
\r
6740 programStats.got_fail = 1;
\r
6743 } else if (thinkOutput[0] != NULLCHAR &&
\r
6744 strncmp(message, " ", 4) == 0) {
\r
6745 unsigned message_len;
\r
6748 while (*p && *p == ' ') p++;
\r
6750 message_len = strlen( p );
\r
6752 /* [AS] Avoid buffer overflow */
\r
6753 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6754 strcat(thinkOutput, " ");
\r
6755 strcat(thinkOutput, p);
\r
6758 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6759 strcat(programStats.movelist, " ");
\r
6760 strcat(programStats.movelist, p);
\r
6763 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6764 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6765 DisplayMove(currentMove - 1);
\r
6766 DisplayAnalysis();
\r
6772 buf1[0] = NULLCHAR;
\r
6774 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6775 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6777 ChessProgramStats cpstats;
\r
6779 if (plyext != ' ' && plyext != '\t') {
\r
6783 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6784 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6785 curscore = -curscore;
\r
6788 cpstats.depth = plylev;
\r
6789 cpstats.nodes = nodes;
\r
6790 cpstats.time = time;
\r
6791 cpstats.score = curscore;
\r
6792 cpstats.got_only_move = 0;
\r
6793 cpstats.movelist[0] = '\0';
\r
6795 if (buf1[0] != NULLCHAR) {
\r
6796 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6799 cpstats.ok_to_send = 0;
\r
6800 cpstats.line_is_book = 0;
\r
6801 cpstats.nr_moves = 0;
\r
6802 cpstats.moves_left = 0;
\r
6804 SendProgramStatsToFrontend( cps, &cpstats );
\r
6811 /* Parse a game score from the character string "game", and
\r
6812 record it as the history of the current game. The game
\r
6813 score is NOT assumed to start from the standard position.
\r
6814 The display is not updated in any way.
\r
6817 ParseGameHistory(game)
\r
6820 ChessMove moveType;
\r
6821 int fromX, fromY, toX, toY, boardIndex;
\r
6824 char buf[MSG_SIZ];
\r
6826 if (appData.debugMode)
\r
6827 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6829 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6830 gameInfo.site = StrSave(appData.icsHost);
\r
6831 gameInfo.date = PGNDate();
\r
6832 gameInfo.round = StrSave("-");
\r
6834 /* Parse out names of players */
\r
6835 while (*game == ' ') game++;
\r
6837 while (*game != ' ') *p++ = *game++;
\r
6839 gameInfo.white = StrSave(buf);
\r
6840 while (*game == ' ') game++;
\r
6842 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6844 gameInfo.black = StrSave(buf);
\r
6847 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6850 yyboardindex = boardIndex;
\r
6851 moveType = (ChessMove) yylex();
\r
6852 switch (moveType) {
\r
6853 case IllegalMove: /* maybe suicide chess, etc. */
\r
6854 if (appData.debugMode) {
\r
6855 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6856 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6857 setbuf(debugFP, NULL);
\r
6859 case WhitePromotionChancellor:
\r
6860 case BlackPromotionChancellor:
\r
6861 case WhitePromotionArchbishop:
\r
6862 case BlackPromotionArchbishop:
\r
6863 case WhitePromotionQueen:
\r
6864 case BlackPromotionQueen:
\r
6865 case WhitePromotionRook:
\r
6866 case BlackPromotionRook:
\r
6867 case WhitePromotionBishop:
\r
6868 case BlackPromotionBishop:
\r
6869 case WhitePromotionKnight:
\r
6870 case BlackPromotionKnight:
\r
6871 case WhitePromotionKing:
\r
6872 case BlackPromotionKing:
\r
6874 case WhiteCapturesEnPassant:
\r
6875 case BlackCapturesEnPassant:
\r
6876 case WhiteKingSideCastle:
\r
6877 case WhiteQueenSideCastle:
\r
6878 case BlackKingSideCastle:
\r
6879 case BlackQueenSideCastle:
\r
6880 case WhiteKingSideCastleWild:
\r
6881 case WhiteQueenSideCastleWild:
\r
6882 case BlackKingSideCastleWild:
\r
6883 case BlackQueenSideCastleWild:
\r
6885 case WhiteHSideCastleFR:
\r
6886 case WhiteASideCastleFR:
\r
6887 case BlackHSideCastleFR:
\r
6888 case BlackASideCastleFR:
\r
6890 fromX = currentMoveString[0] - AAA;
\r
6891 fromY = currentMoveString[1] - ONE;
\r
6892 toX = currentMoveString[2] - AAA;
\r
6893 toY = currentMoveString[3] - ONE;
\r
6894 promoChar = currentMoveString[4];
\r
6898 fromX = moveType == WhiteDrop ?
\r
6899 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6900 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6901 fromY = DROP_RANK;
\r
6902 toX = currentMoveString[2] - AAA;
\r
6903 toY = currentMoveString[3] - ONE;
\r
6904 promoChar = NULLCHAR;
\r
6906 case AmbiguousMove:
\r
6908 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6909 if (appData.debugMode) {
\r
6910 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6911 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6912 setbuf(debugFP, NULL);
\r
6914 DisplayError(buf, 0);
\r
6916 case ImpossibleMove:
\r
6918 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6919 if (appData.debugMode) {
\r
6920 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6921 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6922 setbuf(debugFP, NULL);
\r
6924 DisplayError(buf, 0);
\r
6926 case (ChessMove) 0: /* end of file */
\r
6927 if (boardIndex < backwardMostMove) {
\r
6928 /* Oops, gap. How did that happen? */
\r
6929 DisplayError(_("Gap in move list"), 0);
\r
6932 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6933 if (boardIndex > forwardMostMove) {
\r
6934 forwardMostMove = boardIndex;
\r
6938 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6939 strcat(parseList[boardIndex-1], " ");
\r
6940 strcat(parseList[boardIndex-1], yy_text);
\r
6952 case GameUnfinished:
\r
6953 if (gameMode == IcsExamining) {
\r
6954 if (boardIndex < backwardMostMove) {
\r
6955 /* Oops, gap. How did that happen? */
\r
6958 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6961 gameInfo.result = moveType;
\r
6962 p = strchr(yy_text, '{');
\r
6963 if (p == NULL) p = strchr(yy_text, '(');
\r
6966 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6968 q = strchr(p, *p == '{' ? '}' : ')');
\r
6969 if (q != NULL) *q = NULLCHAR;
\r
6972 gameInfo.resultDetails = StrSave(p);
\r
6975 if (boardIndex >= forwardMostMove &&
\r
6976 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6977 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6980 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6981 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6982 parseList[boardIndex]);
\r
6983 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6984 /* currentMoveString is set as a side-effect of yylex */
\r
6985 strcpy(moveList[boardIndex], currentMoveString);
\r
6986 strcat(moveList[boardIndex], "\n");
\r
6988 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6989 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6990 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6992 case MT_STALEMATE:
\r
6996 if(gameInfo.variant != VariantShogi)
\r
6997 strcat(parseList[boardIndex - 1], "+");
\r
6999 case MT_CHECKMATE:
\r
7000 strcat(parseList[boardIndex - 1], "#");
\r
7007 /* Apply a move to the given board */
\r
7009 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7010 int fromX, fromY, toX, toY;
\r
7014 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7016 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7017 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7018 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7021 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7022 oldEP = epStatus[p-1];
\r
7023 epStatus[p] = EP_NONE;
\r
7025 if( board[toY][toX] != EmptySquare )
\r
7026 epStatus[p] = EP_CAPTURE;
\r
7028 if( board[fromY][fromX] == WhitePawn ) {
\r
7029 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7030 epStatus[p] = EP_PAWN_MOVE;
\r
7031 if( toY-fromY==2) {
\r
7032 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7033 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7034 epStatus[p] = toX | berolina;
\r
7035 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7036 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7037 epStatus[p] = toX;
\r
7040 if( board[fromY][fromX] == BlackPawn ) {
\r
7041 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7042 epStatus[p] = EP_PAWN_MOVE;
\r
7043 if( toY-fromY== -2) {
\r
7044 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7045 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7046 epStatus[p] = toX | berolina;
\r
7047 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7048 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7049 epStatus[p] = toX;
\r
7053 for(i=0; i<nrCastlingRights; i++) {
\r
7054 castlingRights[p][i] = castlingRights[p-1][i];
\r
7055 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7056 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7057 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7062 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7063 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7064 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7066 if (fromX == toX && fromY == toY) return;
\r
7068 if (fromY == DROP_RANK) {
\r
7069 /* must be first */
\r
7070 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7072 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7073 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7074 if(gameInfo.variant == VariantKnightmate)
\r
7075 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7077 /* Code added by Tord: */
\r
7078 /* FRC castling assumed when king captures friendly rook. */
\r
7079 if (board[fromY][fromX] == WhiteKing &&
\r
7080 board[toY][toX] == WhiteRook) {
\r
7081 board[fromY][fromX] = EmptySquare;
\r
7082 board[toY][toX] = EmptySquare;
\r
7084 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7086 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7088 } else if (board[fromY][fromX] == BlackKing &&
\r
7089 board[toY][toX] == BlackRook) {
\r
7090 board[fromY][fromX] = EmptySquare;
\r
7091 board[toY][toX] = EmptySquare;
\r
7093 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7095 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7097 /* End of code added by Tord */
\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_RGHT-1];
\r
7105 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7106 } else if (board[fromY][fromX] == king
\r
7107 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7108 && toY == fromY && toX < fromX-1) {
\r
7109 board[fromY][fromX] = EmptySquare;
\r
7110 board[toY][toX] = king;
\r
7111 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7112 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7113 } else if (board[fromY][fromX] == WhitePawn
\r
7114 && toY == BOARD_HEIGHT-1
\r
7115 && gameInfo.variant != VariantXiangqi
\r
7117 /* white pawn promotion */
\r
7118 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7119 if (board[toY][toX] == EmptySquare) {
\r
7120 board[toY][toX] = WhiteQueen;
\r
7122 if(gameInfo.variant==VariantBughouse ||
\r
7123 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7124 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7125 board[fromY][fromX] = EmptySquare;
\r
7126 } else if ((fromY == BOARD_HEIGHT-4)
\r
7128 && gameInfo.variant != VariantXiangqi
\r
7129 && gameInfo.variant != VariantBerolina
\r
7130 && (board[fromY][fromX] == WhitePawn)
\r
7131 && (board[toY][toX] == EmptySquare)) {
\r
7132 board[fromY][fromX] = EmptySquare;
\r
7133 board[toY][toX] = WhitePawn;
\r
7134 captured = board[toY - 1][toX];
\r
7135 board[toY - 1][toX] = EmptySquare;
\r
7136 } else if ((fromY == BOARD_HEIGHT-4)
\r
7138 && gameInfo.variant == VariantBerolina
\r
7139 && (board[fromY][fromX] == WhitePawn)
\r
7140 && (board[toY][toX] == EmptySquare)) {
\r
7141 board[fromY][fromX] = EmptySquare;
\r
7142 board[toY][toX] = WhitePawn;
\r
7143 if(oldEP & EP_BEROLIN_A) {
\r
7144 captured = board[fromY][fromX-1];
\r
7145 board[fromY][fromX-1] = EmptySquare;
\r
7146 }else{ captured = board[fromY][fromX+1];
\r
7147 board[fromY][fromX+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_RGHT-1];
\r
7155 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7156 } else if (board[fromY][fromX] == king
\r
7157 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7158 && toY == fromY && toX < fromX-1) {
\r
7159 board[fromY][fromX] = EmptySquare;
\r
7160 board[toY][toX] = king;
\r
7161 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7162 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7163 } else if (fromY == 7 && fromX == 3
\r
7164 && board[fromY][fromX] == BlackKing
\r
7165 && toY == 7 && toX == 5) {
\r
7166 board[fromY][fromX] = EmptySquare;
\r
7167 board[toY][toX] = BlackKing;
\r
7168 board[fromY][7] = EmptySquare;
\r
7169 board[toY][4] = BlackRook;
\r
7170 } else if (fromY == 7 && fromX == 3
\r
7171 && board[fromY][fromX] == BlackKing
\r
7172 && toY == 7 && toX == 1) {
\r
7173 board[fromY][fromX] = EmptySquare;
\r
7174 board[toY][toX] = BlackKing;
\r
7175 board[fromY][0] = EmptySquare;
\r
7176 board[toY][2] = BlackRook;
\r
7177 } else if (board[fromY][fromX] == BlackPawn
\r
7179 && gameInfo.variant != VariantXiangqi
\r
7181 /* black pawn promotion */
\r
7182 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7183 if (board[0][toX] == EmptySquare) {
\r
7184 board[0][toX] = BlackQueen;
\r
7186 if(gameInfo.variant==VariantBughouse ||
\r
7187 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7188 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7189 board[fromY][fromX] = EmptySquare;
\r
7190 } else if ((fromY == 3)
\r
7192 && gameInfo.variant != VariantXiangqi
\r
7193 && gameInfo.variant != VariantBerolina
\r
7194 && (board[fromY][fromX] == BlackPawn)
\r
7195 && (board[toY][toX] == EmptySquare)) {
\r
7196 board[fromY][fromX] = EmptySquare;
\r
7197 board[toY][toX] = BlackPawn;
\r
7198 captured = board[toY + 1][toX];
\r
7199 board[toY + 1][toX] = EmptySquare;
\r
7200 } else if ((fromY == 3)
\r
7202 && gameInfo.variant == VariantBerolina
\r
7203 && (board[fromY][fromX] == BlackPawn)
\r
7204 && (board[toY][toX] == EmptySquare)) {
\r
7205 board[fromY][fromX] = EmptySquare;
\r
7206 board[toY][toX] = BlackPawn;
\r
7207 if(oldEP & EP_BEROLIN_A) {
\r
7208 captured = board[fromY][fromX-1];
\r
7209 board[fromY][fromX-1] = EmptySquare;
\r
7210 }else{ captured = board[fromY][fromX+1];
\r
7211 board[fromY][fromX+1] = EmptySquare;
\r
7214 board[toY][toX] = board[fromY][fromX];
\r
7215 board[fromY][fromX] = EmptySquare;
\r
7218 /* [HGM] now we promote for Shogi, if needed */
\r
7219 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7220 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7223 if (gameInfo.holdingsWidth != 0) {
\r
7225 /* !!A lot more code needs to be written to support holdings */
\r
7226 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7227 /* penultimate board files, so they are automaticlly stored */
\r
7228 /* in the game history. */
\r
7229 if (fromY == DROP_RANK) {
\r
7230 /* Delete from holdings, by decreasing count */
\r
7231 /* and erasing image if necessary */
\r
7233 if(p < (int) BlackPawn) { /* white drop */
\r
7234 p -= (int)WhitePawn;
\r
7235 if(p >= gameInfo.holdingsSize) p = 0;
\r
7236 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7237 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7238 } else { /* black drop */
\r
7239 p -= (int)BlackPawn;
\r
7240 if(p >= gameInfo.holdingsSize) p = 0;
\r
7241 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7242 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7245 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7246 && gameInfo.variant != VariantBughouse ) {
\r
7247 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7248 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7249 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7250 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7252 p = (int) captured;
\r
7253 if (p >= (int) BlackPawn) {
\r
7254 p -= (int)BlackPawn;
\r
7255 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7256 /* in Shogi restore piece to its original first */
\r
7257 captured = (ChessSquare) (DEMOTED captured);
\r
7260 p = PieceToNumber((ChessSquare)p);
\r
7261 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7262 board[p][BOARD_WIDTH-2]++;
\r
7263 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7265 p -= (int)WhitePawn;
\r
7266 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7267 captured = (ChessSquare) (DEMOTED captured);
\r
7270 p = PieceToNumber((ChessSquare)p);
\r
7271 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7272 board[BOARD_HEIGHT-1-p][1]++;
\r
7273 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7277 } else if (gameInfo.variant == VariantAtomic) {
\r
7278 if (captured != EmptySquare) {
\r
7280 for (y = toY-1; y <= toY+1; y++) {
\r
7281 for (x = toX-1; x <= toX+1; x++) {
\r
7282 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7283 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7284 board[y][x] = EmptySquare;
\r
7288 board[toY][toX] = EmptySquare;
\r
7291 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7292 /* [HGM] Shogi promotions */
\r
7293 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7298 /* Updates forwardMostMove */
\r
7300 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7301 int fromX, fromY, toX, toY;
\r
7304 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7306 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7307 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7308 piece = boards[forwardMostMove][fromY][fromX];
\r
7309 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7310 if(gameInfo.variant == VariantKnightmate)
\r
7311 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7312 if(forwardMostMove == 0) {
\r
7313 if(blackPlaysFirst)
\r
7314 fprintf(serverMoves, "%s;", second.tidy);
\r
7315 fprintf(serverMoves, "%s;", first.tidy);
\r
7316 if(!blackPlaysFirst)
\r
7317 fprintf(serverMoves, "%s;", second.tidy);
\r
7318 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7319 lastLoadFlag = loadFlag;
\r
7320 // print base move
\r
7321 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7322 // print castling suffix
\r
7323 if( toY == fromY && piece == king ) {
\r
7325 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7327 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7330 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7331 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7332 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7334 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7335 // promotion suffix
\r
7336 if(promoChar != NULLCHAR)
\r
7337 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7339 fprintf(serverMoves, "/%d/%d",
\r
7340 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7341 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7342 else timeLeft = blackTimeRemaining/1000;
\r
7343 fprintf(serverMoves, "/%d", timeLeft);
\r
7345 fflush(serverMoves);
\r
7348 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7349 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7354 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7355 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7356 if (commentList[forwardMostMove+1] != NULL) {
\r
7357 free(commentList[forwardMostMove+1]);
\r
7358 commentList[forwardMostMove+1] = NULL;
\r
7360 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7361 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7362 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7363 gameInfo.result = GameUnfinished;
\r
7364 if (gameInfo.resultDetails != NULL) {
\r
7365 free(gameInfo.resultDetails);
\r
7366 gameInfo.resultDetails = NULL;
\r
7368 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7369 moveList[forwardMostMove - 1]);
\r
7370 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7371 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7372 fromY, fromX, toY, toX, promoChar,
\r
7373 parseList[forwardMostMove - 1]);
\r
7374 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7375 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7376 castlingRights[forwardMostMove]) ) {
\r
7378 case MT_STALEMATE:
\r
7382 if(gameInfo.variant != VariantShogi)
\r
7383 strcat(parseList[forwardMostMove - 1], "+");
\r
7385 case MT_CHECKMATE:
\r
7386 strcat(parseList[forwardMostMove - 1], "#");
\r
7389 if (appData.debugMode) {
\r
7390 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7395 /* Updates currentMove if not pausing */
\r
7397 ShowMove(fromX, fromY, toX, toY)
\r
7399 int instant = (gameMode == PlayFromGameFile) ?
\r
7400 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7401 if(appData.noGUI) return;
\r
7402 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7404 if (forwardMostMove == currentMove + 1) {
\r
7405 AnimateMove(boards[forwardMostMove - 1],
\r
7406 fromX, fromY, toX, toY);
\r
7408 if (appData.highlightLastMove) {
\r
7409 SetHighlights(fromX, fromY, toX, toY);
\r
7412 currentMove = forwardMostMove;
\r
7415 if (instant) return;
\r
7417 DisplayMove(currentMove - 1);
\r
7418 DrawPosition(FALSE, boards[currentMove]);
\r
7419 DisplayBothClocks();
\r
7420 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7423 void SendEgtPath(ChessProgramState *cps)
\r
7424 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7425 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7427 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7430 char c, *q = name+1, *r, *s;
\r
7432 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7433 while(*p && *p != ',') *q++ = *p++;
\r
7434 *q++ = ':'; *q = 0;
\r
7435 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7436 strcmp(name, ",nalimov:") == 0 ) {
\r
7437 // take nalimov path from the menu-changeable option first, if it is defined
\r
7438 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7439 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7441 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7442 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7443 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7444 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7445 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7446 c = *r; *r = 0; // temporarily null-terminate path info
\r
7447 *--q = 0; // strip of trailig ':' from name
\r
7448 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7450 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7452 if(*p == ',') p++; // read away comma to position for next format name
\r
7457 InitChessProgram(cps, setup)
\r
7458 ChessProgramState *cps;
\r
7459 int setup; /* [HGM] needed to setup FRC opening position */
\r
7461 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7462 if (appData.noChessProgram) return;
\r
7463 hintRequested = FALSE;
\r
7464 bookRequested = FALSE;
\r
7466 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7467 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7468 if(cps->memSize) { /* [HGM] memory */
\r
7469 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7470 SendToProgram(buf, cps);
\r
7472 SendEgtPath(cps); /* [HGM] EGT */
\r
7473 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7474 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7475 SendToProgram(buf, cps);
\r
7478 SendToProgram(cps->initString, cps);
\r
7479 if (gameInfo.variant != VariantNormal &&
\r
7480 gameInfo.variant != VariantLoadable
\r
7481 /* [HGM] also send variant if board size non-standard */
\r
7482 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7484 char *v = VariantName(gameInfo.variant);
\r
7485 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7486 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7487 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7488 DisplayFatalError(buf, 0, 1);
\r
7492 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7493 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7494 if( gameInfo.variant == VariantXiangqi )
\r
7495 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7496 if( gameInfo.variant == VariantShogi )
\r
7497 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7498 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7499 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7500 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7501 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7502 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7503 if( gameInfo.variant == VariantCourier )
\r
7504 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7505 if( gameInfo.variant == VariantSuper )
\r
7506 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7507 if( gameInfo.variant == VariantGreat )
\r
7508 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7511 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7512 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7513 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7514 if(StrStr(cps->variants, b) == NULL) {
\r
7515 // specific sized variant not known, check if general sizing allowed
\r
7516 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7517 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7518 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7519 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7520 DisplayFatalError(buf, 0, 1);
\r
7523 /* [HGM] here we really should compare with the maximum supported board size */
\r
7526 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7527 sprintf(buf, "variant %s\n", b);
\r
7528 SendToProgram(buf, cps);
\r
7530 currentlyInitializedVariant = gameInfo.variant;
\r
7532 /* [HGM] send opening position in FRC to first engine */
\r
7534 SendToProgram("force\n", cps);
\r
7535 SendBoard(cps, 0);
\r
7536 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7537 setboardSpoiledMachineBlack = 1;
\r
7540 if (cps->sendICS) {
\r
7541 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7542 SendToProgram(buf, cps);
\r
7544 cps->maybeThinking = FALSE;
\r
7545 cps->offeredDraw = 0;
\r
7546 if (!appData.icsActive) {
\r
7547 SendTimeControl(cps, movesPerSession, timeControl,
\r
7548 timeIncrement, appData.searchDepth,
\r
7551 if (appData.showThinking
\r
7552 // [HGM] thinking: four options require thinking output to be sent
\r
7553 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7555 SendToProgram("post\n", cps);
\r
7557 SendToProgram("hard\n", cps);
\r
7558 if (!appData.ponderNextMove) {
\r
7559 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7560 it without being sure what state we are in first. "hard"
\r
7561 is not a toggle, so that one is OK.
\r
7563 SendToProgram("easy\n", cps);
\r
7565 if (cps->usePing) {
\r
7566 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7567 SendToProgram(buf, cps);
\r
7569 cps->initDone = TRUE;
\r
7574 StartChessProgram(cps)
\r
7575 ChessProgramState *cps;
\r
7577 char buf[MSG_SIZ];
\r
7580 if (appData.noChessProgram) return;
\r
7581 cps->initDone = FALSE;
\r
7583 if (strcmp(cps->host, "localhost") == 0) {
\r
7584 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7585 } else if (*appData.remoteShell == NULLCHAR) {
\r
7586 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7588 if (*appData.remoteUser == NULLCHAR) {
\r
7589 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7592 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7593 cps->host, appData.remoteUser, cps->program);
\r
7595 err = StartChildProcess(buf, "", &cps->pr);
\r
7599 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7600 DisplayFatalError(buf, err, 1);
\r
7606 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7607 if (cps->protocolVersion > 1) {
\r
7608 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7609 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7610 cps->comboCnt = 0; // and values of combo boxes
\r
7611 SendToProgram(buf, cps);
\r
7613 SendToProgram("xboard\n", cps);
\r
7619 TwoMachinesEventIfReady P((void))
\r
7621 if (first.lastPing != first.lastPong) {
\r
7622 DisplayMessage("", _("Waiting for first chess program"));
\r
7623 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7626 if (second.lastPing != second.lastPong) {
\r
7627 DisplayMessage("", _("Waiting for second chess program"));
\r
7628 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7632 TwoMachinesEvent();
\r
7636 NextMatchGame P((void))
\r
7638 int index; /* [HGM] autoinc: step lod index during match */
\r
7639 Reset(FALSE, TRUE);
\r
7640 if (*appData.loadGameFile != NULLCHAR) {
\r
7641 index = appData.loadGameIndex;
\r
7642 if(index < 0) { // [HGM] autoinc
\r
7643 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7644 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7646 LoadGameFromFile(appData.loadGameFile,
\r
7648 appData.loadGameFile, FALSE);
\r
7649 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7650 index = appData.loadPositionIndex;
\r
7651 if(index < 0) { // [HGM] autoinc
\r
7652 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7653 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7655 LoadPositionFromFile(appData.loadPositionFile,
\r
7657 appData.loadPositionFile);
\r
7659 TwoMachinesEventIfReady();
\r
7662 void UserAdjudicationEvent( int result )
\r
7664 ChessMove gameResult = GameIsDrawn;
\r
7666 if( result > 0 ) {
\r
7667 gameResult = WhiteWins;
\r
7669 else if( result < 0 ) {
\r
7670 gameResult = BlackWins;
\r
7673 if( gameMode == TwoMachinesPlay ) {
\r
7674 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7680 GameEnds(result, resultDetails, whosays)
\r
7682 char *resultDetails;
\r
7685 GameMode nextGameMode;
\r
7687 char buf[MSG_SIZ];
\r
7689 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7692 if (appData.debugMode) {
\r
7693 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7694 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7697 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7698 /* If we are playing on ICS, the server decides when the
\r
7699 game is over, but the engine can offer to draw, claim
\r
7700 a draw, or resign.
\r
7703 if (appData.zippyPlay && first.initDone) {
\r
7704 if (result == GameIsDrawn) {
\r
7705 /* In case draw still needs to be claimed */
\r
7706 SendToICS(ics_prefix);
\r
7707 SendToICS("draw\n");
\r
7708 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7709 SendToICS(ics_prefix);
\r
7710 SendToICS("resign\n");
\r
7714 endingGame = 0; /* [HGM] crash */
\r
7718 /* If we're loading the game from a file, stop */
\r
7719 if (whosays == GE_FILE) {
\r
7720 (void) StopLoadGameTimer();
\r
7721 gameFileFP = NULL;
\r
7724 /* Cancel draw offers */
\r
7725 first.offeredDraw = second.offeredDraw = 0;
\r
7727 /* If this is an ICS game, only ICS can really say it's done;
\r
7728 if not, anyone can. */
\r
7729 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7730 gameMode == IcsPlayingBlack ||
\r
7731 gameMode == IcsObserving ||
\r
7732 gameMode == IcsExamining);
\r
7734 if (!isIcsGame || whosays == GE_ICS) {
\r
7735 /* OK -- not an ICS game, or ICS said it was done */
\r
7737 if (!isIcsGame && !appData.noChessProgram)
\r
7738 SetUserThinkingEnables();
\r
7740 /* [HGM] if a machine claims the game end we verify this claim */
\r
7741 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7742 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7744 ChessMove trueResult = (ChessMove) -1;
\r
7746 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7747 first.twoMachinesColor[0] :
\r
7748 second.twoMachinesColor[0] ;
\r
7750 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
\r
7751 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
\r
7752 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7753 trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)
\r
7754 ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!
\r
7756 if(epStatus[forwardMostMove] == EP_STALEMATE) {
\r
7757 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
\r
7758 if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide ||
\r
7759 gameInfo.variant == VariantLosers) // [HGM] losers: in giveaway variants stalemate wins
\r
7760 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
\r
7763 // now verify win claims, but not in drop games, as we don't understand those yet
\r
7764 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
\r
7765 || gameInfo.variant == VariantGreat) &&
\r
7766 (result == WhiteWins && claimer == 'w' ||
\r
7767 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
\r
7768 if (appData.debugMode) {
\r
7769 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7770 result, epStatus[forwardMostMove], forwardMostMove);
\r
7772 if(result != trueResult) {
\r
7773 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7774 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7775 resultDetails = buf;
\r
7778 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7779 && (forwardMostMove <= backwardMostMove ||
\r
7780 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7781 (claimer=='b')==(forwardMostMove&1))
\r
7783 /* [HGM] verify: draws that were not flagged are false claims */
\r
7784 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7785 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7786 resultDetails = buf;
\r
7788 /* (Claiming a loss is accepted no questions asked!) */
\r
7790 /* [HGM] bare: don't allow bare King to win */
\r
7791 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7792 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7793 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7794 && result != GameIsDrawn)
\r
7795 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7796 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7797 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7798 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7800 if (appData.debugMode) {
\r
7801 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7802 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7805 result = GameIsDrawn;
\r
7806 sprintf(buf, "%s but bare king", resultDetails);
\r
7807 resultDetails = buf;
\r
7813 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7814 if(result==WhiteWins) c = '+';
\r
7815 if(result==BlackWins) c = '-';
\r
7816 if(resultDetails != NULL)
\r
7817 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7819 if (resultDetails != NULL) {
\r
7820 gameInfo.result = result;
\r
7821 gameInfo.resultDetails = StrSave(resultDetails);
\r
7823 /* display last move only if game was not loaded from file */
\r
7824 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7825 DisplayMove(currentMove - 1);
\r
7827 if (forwardMostMove != 0) {
\r
7828 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7829 if (*appData.saveGameFile != NULLCHAR) {
\r
7830 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7831 } else if (appData.autoSaveGames) {
\r
7834 if (*appData.savePositionFile != NULLCHAR) {
\r
7835 SavePositionToFile(appData.savePositionFile);
\r
7840 /* Tell program how game ended in case it is learning */
\r
7841 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7842 /* engine died and we got here through time loss. In that */
\r
7843 /* case we will get a fatal error writing the pipe, which */
\r
7844 /* would otherwise lose us the PGN. */
\r
7845 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7846 /* output during GameEnds should never be fatal anymore */
\r
7847 if (gameMode == MachinePlaysWhite ||
\r
7848 gameMode == MachinePlaysBlack ||
\r
7849 gameMode == TwoMachinesPlay ||
\r
7850 gameMode == IcsPlayingWhite ||
\r
7851 gameMode == IcsPlayingBlack ||
\r
7852 gameMode == BeginningOfGame) {
\r
7853 char buf[MSG_SIZ];
\r
7854 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7856 if (first.pr != NoProc) {
\r
7857 SendToProgram(buf, &first);
\r
7859 if (second.pr != NoProc &&
\r
7860 gameMode == TwoMachinesPlay) {
\r
7861 SendToProgram(buf, &second);
\r
7866 if (appData.icsActive) {
\r
7867 if (appData.quietPlay &&
\r
7868 (gameMode == IcsPlayingWhite ||
\r
7869 gameMode == IcsPlayingBlack)) {
\r
7870 SendToICS(ics_prefix);
\r
7871 SendToICS("set shout 1\n");
\r
7873 nextGameMode = IcsIdle;
\r
7874 ics_user_moved = FALSE;
\r
7875 /* clean up premove. It's ugly when the game has ended and the
\r
7876 * premove highlights are still on the board.
\r
7879 gotPremove = FALSE;
\r
7880 ClearPremoveHighlights();
\r
7881 DrawPosition(FALSE, boards[currentMove]);
\r
7883 if (whosays == GE_ICS) {
\r
7886 if (gameMode == IcsPlayingWhite)
\r
7887 PlayIcsWinSound();
\r
7888 else if(gameMode == IcsPlayingBlack)
\r
7889 PlayIcsLossSound();
\r
7892 if (gameMode == IcsPlayingBlack)
\r
7893 PlayIcsWinSound();
\r
7894 else if(gameMode == IcsPlayingWhite)
\r
7895 PlayIcsLossSound();
\r
7898 PlayIcsDrawSound();
\r
7901 PlayIcsUnfinishedSound();
\r
7904 } else if (gameMode == EditGame ||
\r
7905 gameMode == PlayFromGameFile ||
\r
7906 gameMode == AnalyzeMode ||
\r
7907 gameMode == AnalyzeFile) {
\r
7908 nextGameMode = gameMode;
\r
7910 nextGameMode = EndOfGame;
\r
7915 nextGameMode = gameMode;
\r
7918 if (appData.noChessProgram) {
\r
7919 gameMode = nextGameMode;
\r
7921 endingGame = 0; /* [HGM] crash */
\r
7925 if (first.reuse) {
\r
7926 /* Put first chess program into idle state */
\r
7927 if (first.pr != NoProc &&
\r
7928 (gameMode == MachinePlaysWhite ||
\r
7929 gameMode == MachinePlaysBlack ||
\r
7930 gameMode == TwoMachinesPlay ||
\r
7931 gameMode == IcsPlayingWhite ||
\r
7932 gameMode == IcsPlayingBlack ||
\r
7933 gameMode == BeginningOfGame)) {
\r
7934 SendToProgram("force\n", &first);
\r
7935 if (first.usePing) {
\r
7936 char buf[MSG_SIZ];
\r
7937 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7938 SendToProgram(buf, &first);
\r
7941 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7942 /* Kill off first chess program */
\r
7943 if (first.isr != NULL)
\r
7944 RemoveInputSource(first.isr);
\r
7947 if (first.pr != NoProc) {
\r
7948 ExitAnalyzeMode();
\r
7949 DoSleep( appData.delayBeforeQuit );
\r
7950 SendToProgram("quit\n", &first);
\r
7951 DoSleep( appData.delayAfterQuit );
\r
7952 DestroyChildProcess(first.pr, first.useSigterm);
\r
7954 first.pr = NoProc;
\r
7956 if (second.reuse) {
\r
7957 /* Put second chess program into idle state */
\r
7958 if (second.pr != NoProc &&
\r
7959 gameMode == TwoMachinesPlay) {
\r
7960 SendToProgram("force\n", &second);
\r
7961 if (second.usePing) {
\r
7962 char buf[MSG_SIZ];
\r
7963 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7964 SendToProgram(buf, &second);
\r
7967 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7968 /* Kill off second chess program */
\r
7969 if (second.isr != NULL)
\r
7970 RemoveInputSource(second.isr);
\r
7971 second.isr = NULL;
\r
7973 if (second.pr != NoProc) {
\r
7974 DoSleep( appData.delayBeforeQuit );
\r
7975 SendToProgram("quit\n", &second);
\r
7976 DoSleep( appData.delayAfterQuit );
\r
7977 DestroyChildProcess(second.pr, second.useSigterm);
\r
7979 second.pr = NoProc;
\r
7982 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7985 if (first.twoMachinesColor[0] == 'w') {
\r
7986 first.matchWins++;
\r
7988 second.matchWins++;
\r
7992 if (first.twoMachinesColor[0] == 'b') {
\r
7993 first.matchWins++;
\r
7995 second.matchWins++;
\r
8001 if (matchGame < appData.matchGames) {
\r
8003 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8004 tmp = first.twoMachinesColor;
\r
8005 first.twoMachinesColor = second.twoMachinesColor;
\r
8006 second.twoMachinesColor = tmp;
\r
8008 gameMode = nextGameMode;
\r
8010 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8011 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8012 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8013 endingGame = 0; /* [HGM] crash */
\r
8016 char buf[MSG_SIZ];
\r
8017 gameMode = nextGameMode;
\r
8018 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8019 first.tidy, second.tidy,
\r
8020 first.matchWins, second.matchWins,
\r
8021 appData.matchGames - (first.matchWins + second.matchWins));
\r
8022 DisplayFatalError(buf, 0, 0);
\r
8025 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8026 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8027 ExitAnalyzeMode();
\r
8028 gameMode = nextGameMode;
\r
8030 endingGame = 0; /* [HGM] crash */
\r
8033 /* Assumes program was just initialized (initString sent).
\r
8034 Leaves program in force mode. */
\r
8036 FeedMovesToProgram(cps, upto)
\r
8037 ChessProgramState *cps;
\r
8042 if (appData.debugMode)
\r
8043 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8044 startedFromSetupPosition ? "position and " : "",
\r
8045 backwardMostMove, upto, cps->which);
\r
8046 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8047 // [HGM] variantswitch: make engine aware of new variant
\r
8048 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8049 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8050 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8051 SendToProgram(buf, cps);
\r
8052 currentlyInitializedVariant = gameInfo.variant;
\r
8054 SendToProgram("force\n", cps);
\r
8055 if (startedFromSetupPosition) {
\r
8056 SendBoard(cps, backwardMostMove);
\r
8057 if (appData.debugMode) {
\r
8058 fprintf(debugFP, "feedMoves\n");
\r
8061 for (i = backwardMostMove; i < upto; i++) {
\r
8062 SendMoveToProgram(i, cps);
\r
8068 ResurrectChessProgram()
\r
8070 /* The chess program may have exited.
\r
8071 If so, restart it and feed it all the moves made so far. */
\r
8073 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8075 StartChessProgram(&first);
\r
8076 InitChessProgram(&first, FALSE);
\r
8077 FeedMovesToProgram(&first, currentMove);
\r
8079 if (!first.sendTime) {
\r
8080 /* can't tell gnuchess what its clock should read,
\r
8081 so we bow to its notion. */
\r
8083 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8084 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8087 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8088 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8089 SendToProgram("analyze\n", &first);
\r
8090 first.analyzing = TRUE;
\r
8095 * Button procedures
\r
8098 Reset(redraw, init)
\r
8103 if (appData.debugMode) {
\r
8104 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8105 redraw, init, gameMode);
\r
8107 pausing = pauseExamInvalid = FALSE;
\r
8108 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8110 whiteFlag = blackFlag = FALSE;
\r
8111 userOfferedDraw = FALSE;
\r
8112 hintRequested = bookRequested = FALSE;
\r
8113 first.maybeThinking = FALSE;
\r
8114 second.maybeThinking = FALSE;
\r
8115 first.bookSuspend = FALSE; // [HGM] book
\r
8116 second.bookSuspend = FALSE;
\r
8117 thinkOutput[0] = NULLCHAR;
\r
8118 lastHint[0] = NULLCHAR;
\r
8119 ClearGameInfo(&gameInfo);
\r
8120 gameInfo.variant = StringToVariant(appData.variant);
\r
8121 ics_user_moved = ics_clock_paused = FALSE;
\r
8122 ics_getting_history = H_FALSE;
\r
8124 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8125 ClearProgramStats();
\r
8126 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8129 ClearHighlights();
\r
8130 flipView = appData.flipView;
\r
8131 ClearPremoveHighlights();
\r
8132 gotPremove = FALSE;
\r
8133 alarmSounded = FALSE;
\r
8135 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8136 if(appData.serverMovesName != NULL) {
\r
8137 /* [HGM] prepare to make moves file for broadcasting */
\r
8138 clock_t t = clock();
\r
8139 if(serverMoves != NULL) fclose(serverMoves);
\r
8140 serverMoves = fopen(appData.serverMovesName, "r");
\r
8141 if(serverMoves != NULL) {
\r
8142 fclose(serverMoves);
\r
8143 /* delay 15 sec before overwriting, so all clients can see end */
\r
8144 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8146 serverMoves = fopen(appData.serverMovesName, "w");
\r
8149 ExitAnalyzeMode();
\r
8150 gameMode = BeginningOfGame;
\r
8152 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8153 InitPosition(redraw);
\r
8154 for (i = 0; i < MAX_MOVES; i++) {
\r
8155 if (commentList[i] != NULL) {
\r
8156 free(commentList[i]);
\r
8157 commentList[i] = NULL;
\r
8161 timeRemaining[0][0] = whiteTimeRemaining;
\r
8162 timeRemaining[1][0] = blackTimeRemaining;
\r
8163 if (first.pr == NULL) {
\r
8164 StartChessProgram(&first);
\r
8167 InitChessProgram(&first, startedFromSetupPosition);
\r
8170 DisplayMessage("", "");
\r
8171 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8175 AutoPlayGameLoop()
\r
8178 if (!AutoPlayOneMove())
\r
8180 if (matchMode || appData.timeDelay == 0)
\r
8182 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8184 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8193 int fromX, fromY, toX, toY;
\r
8195 if (appData.debugMode) {
\r
8196 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8199 if (gameMode != PlayFromGameFile)
\r
8202 if (currentMove >= forwardMostMove) {
\r
8203 gameMode = EditGame;
\r
8206 /* [AS] Clear current move marker at the end of a game */
\r
8207 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8212 toX = moveList[currentMove][2] - AAA;
\r
8213 toY = moveList[currentMove][3] - ONE;
\r
8215 if (moveList[currentMove][1] == '@') {
\r
8216 if (appData.highlightLastMove) {
\r
8217 SetHighlights(-1, -1, toX, toY);
\r
8220 fromX = moveList[currentMove][0] - AAA;
\r
8221 fromY = moveList[currentMove][1] - ONE;
\r
8223 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8225 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8227 if (appData.highlightLastMove) {
\r
8228 SetHighlights(fromX, fromY, toX, toY);
\r
8231 DisplayMove(currentMove);
\r
8232 SendMoveToProgram(currentMove++, &first);
\r
8233 DisplayBothClocks();
\r
8234 DrawPosition(FALSE, boards[currentMove]);
\r
8235 // [HGM] PV info: always display, routine tests if empty
\r
8236 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8242 LoadGameOneMove(readAhead)
\r
8243 ChessMove readAhead;
\r
8245 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8246 char promoChar = NULLCHAR;
\r
8247 ChessMove moveType;
\r
8248 char move[MSG_SIZ];
\r
8251 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8252 gameMode != AnalyzeMode && gameMode != Training) {
\r
8253 gameFileFP = NULL;
\r
8257 yyboardindex = forwardMostMove;
\r
8258 if (readAhead != (ChessMove)0) {
\r
8259 moveType = readAhead;
\r
8261 if (gameFileFP == NULL)
\r
8263 moveType = (ChessMove) yylex();
\r
8267 switch (moveType) {
\r
8269 if (appData.debugMode)
\r
8270 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8272 if (*p == '{' || *p == '[' || *p == '(') {
\r
8273 p[strlen(p) - 1] = NULLCHAR;
\r
8277 /* append the comment but don't display it */
\r
8278 while (*p == '\n') p++;
\r
8279 AppendComment(currentMove, p);
\r
8282 case WhiteCapturesEnPassant:
\r
8283 case BlackCapturesEnPassant:
\r
8284 case WhitePromotionChancellor:
\r
8285 case BlackPromotionChancellor:
\r
8286 case WhitePromotionArchbishop:
\r
8287 case BlackPromotionArchbishop:
\r
8288 case WhitePromotionCentaur:
\r
8289 case BlackPromotionCentaur:
\r
8290 case WhitePromotionQueen:
\r
8291 case BlackPromotionQueen:
\r
8292 case WhitePromotionRook:
\r
8293 case BlackPromotionRook:
\r
8294 case WhitePromotionBishop:
\r
8295 case BlackPromotionBishop:
\r
8296 case WhitePromotionKnight:
\r
8297 case BlackPromotionKnight:
\r
8298 case WhitePromotionKing:
\r
8299 case BlackPromotionKing:
\r
8301 case WhiteKingSideCastle:
\r
8302 case WhiteQueenSideCastle:
\r
8303 case BlackKingSideCastle:
\r
8304 case BlackQueenSideCastle:
\r
8305 case WhiteKingSideCastleWild:
\r
8306 case WhiteQueenSideCastleWild:
\r
8307 case BlackKingSideCastleWild:
\r
8308 case BlackQueenSideCastleWild:
\r
8310 case WhiteHSideCastleFR:
\r
8311 case WhiteASideCastleFR:
\r
8312 case BlackHSideCastleFR:
\r
8313 case BlackASideCastleFR:
\r
8315 if (appData.debugMode)
\r
8316 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8317 fromX = currentMoveString[0] - AAA;
\r
8318 fromY = currentMoveString[1] - ONE;
\r
8319 toX = currentMoveString[2] - AAA;
\r
8320 toY = currentMoveString[3] - ONE;
\r
8321 promoChar = currentMoveString[4];
\r
8326 if (appData.debugMode)
\r
8327 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8328 fromX = moveType == WhiteDrop ?
\r
8329 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8330 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8331 fromY = DROP_RANK;
\r
8332 toX = currentMoveString[2] - AAA;
\r
8333 toY = currentMoveString[3] - ONE;
\r
8339 case GameUnfinished:
\r
8340 if (appData.debugMode)
\r
8341 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8342 p = strchr(yy_text, '{');
\r
8343 if (p == NULL) p = strchr(yy_text, '(');
\r
8346 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8348 q = strchr(p, *p == '{' ? '}' : ')');
\r
8349 if (q != NULL) *q = NULLCHAR;
\r
8352 GameEnds(moveType, p, GE_FILE);
\r
8354 if (cmailMsgLoaded) {
\r
8355 ClearHighlights();
\r
8356 flipView = WhiteOnMove(currentMove);
\r
8357 if (moveType == GameUnfinished) flipView = !flipView;
\r
8358 if (appData.debugMode)
\r
8359 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8363 case (ChessMove) 0: /* end of file */
\r
8364 if (appData.debugMode)
\r
8365 fprintf(debugFP, "Parser hit end of file\n");
\r
8366 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8367 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8371 case MT_CHECKMATE:
\r
8372 if (WhiteOnMove(currentMove)) {
\r
8373 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8375 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8378 case MT_STALEMATE:
\r
8379 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8385 case MoveNumberOne:
\r
8386 if (lastLoadGameStart == GNUChessGame) {
\r
8387 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8388 if (appData.debugMode)
\r
8389 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8390 yy_text, (int) moveType);
\r
8391 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8393 /* else fall thru */
\r
8396 case GNUChessGame:
\r
8398 /* Reached start of next game in file */
\r
8399 if (appData.debugMode)
\r
8400 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8401 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8402 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8406 case MT_CHECKMATE:
\r
8407 if (WhiteOnMove(currentMove)) {
\r
8408 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8410 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8413 case MT_STALEMATE:
\r
8414 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8420 case PositionDiagram: /* should not happen; ignore */
\r
8421 case ElapsedTime: /* ignore */
\r
8422 case NAG: /* ignore */
\r
8423 if (appData.debugMode)
\r
8424 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8425 yy_text, (int) moveType);
\r
8426 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8429 if (appData.testLegality) {
\r
8430 if (appData.debugMode)
\r
8431 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8432 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8433 (forwardMostMove / 2) + 1,
\r
8434 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8435 DisplayError(move, 0);
\r
8438 if (appData.debugMode)
\r
8439 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8440 yy_text, currentMoveString);
\r
8441 fromX = currentMoveString[0] - AAA;
\r
8442 fromY = currentMoveString[1] - ONE;
\r
8443 toX = currentMoveString[2] - AAA;
\r
8444 toY = currentMoveString[3] - ONE;
\r
8445 promoChar = currentMoveString[4];
\r
8449 case AmbiguousMove:
\r
8450 if (appData.debugMode)
\r
8451 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8452 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8453 (forwardMostMove / 2) + 1,
\r
8454 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8455 DisplayError(move, 0);
\r
8460 case ImpossibleMove:
\r
8461 if (appData.debugMode)
\r
8462 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8463 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8464 (forwardMostMove / 2) + 1,
\r
8465 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8466 DisplayError(move, 0);
\r
8472 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8473 DrawPosition(FALSE, boards[currentMove]);
\r
8474 DisplayBothClocks();
\r
8475 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8476 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8478 (void) StopLoadGameTimer();
\r
8479 gameFileFP = NULL;
\r
8480 cmailOldMove = forwardMostMove;
\r
8483 /* currentMoveString is set as a side-effect of yylex */
\r
8484 strcat(currentMoveString, "\n");
\r
8485 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8487 thinkOutput[0] = NULLCHAR;
\r
8488 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8489 currentMove = forwardMostMove;
\r
8494 /* Load the nth game from the given file */
\r
8496 LoadGameFromFile(filename, n, title, useList)
\r
8500 /*Boolean*/ int useList;
\r
8503 char buf[MSG_SIZ];
\r
8505 if (strcmp(filename, "-") == 0) {
\r
8509 f = fopen(filename, "rb");
\r
8511 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8512 DisplayError(buf, errno);
\r
8516 if (fseek(f, 0, 0) == -1) {
\r
8517 /* f is not seekable; probably a pipe */
\r
8520 if (useList && n == 0) {
\r
8521 int error = GameListBuild(f);
\r
8523 DisplayError(_("Cannot build game list"), error);
\r
8524 } else if (!ListEmpty(&gameList) &&
\r
8525 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8526 GameListPopUp(f, title);
\r
8529 GameListDestroy();
\r
8532 if (n == 0) n = 1;
\r
8533 return LoadGame(f, n, title, FALSE);
\r
8538 MakeRegisteredMove()
\r
8540 int fromX, fromY, toX, toY;
\r
8542 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8543 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8546 if (appData.debugMode)
\r
8547 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8548 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8550 thinkOutput[0] = NULLCHAR;
\r
8551 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8552 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8553 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8554 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8555 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8556 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8557 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8558 ShowMove(fromX, fromY, toX, toY);
\r
8560 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8561 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8566 case MT_CHECKMATE:
\r
8567 if (WhiteOnMove(currentMove)) {
\r
8568 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8570 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8574 case MT_STALEMATE:
\r
8575 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8581 case CMAIL_RESIGN:
\r
8582 if (WhiteOnMove(currentMove)) {
\r
8583 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8585 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8589 case CMAIL_ACCEPT:
\r
8590 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8601 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8603 CmailLoadGame(f, gameNumber, title, useList)
\r
8611 if (gameNumber > nCmailGames) {
\r
8612 DisplayError(_("No more games in this message"), 0);
\r
8615 if (f == lastLoadGameFP) {
\r
8616 int offset = gameNumber - lastLoadGameNumber;
\r
8617 if (offset == 0) {
\r
8618 cmailMsg[0] = NULLCHAR;
\r
8619 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8620 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8621 nCmailMovesRegistered--;
\r
8623 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8624 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8625 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8628 if (! RegisterMove()) return FALSE;
\r
8632 retVal = LoadGame(f, gameNumber, title, useList);
\r
8634 /* Make move registered during previous look at this game, if any */
\r
8635 MakeRegisteredMove();
\r
8637 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8638 commentList[currentMove]
\r
8639 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8640 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8646 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8648 ReloadGame(offset)
\r
8651 int gameNumber = lastLoadGameNumber + offset;
\r
8652 if (lastLoadGameFP == NULL) {
\r
8653 DisplayError(_("No game has been loaded yet"), 0);
\r
8656 if (gameNumber <= 0) {
\r
8657 DisplayError(_("Can't back up any further"), 0);
\r
8660 if (cmailMsgLoaded) {
\r
8661 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8662 lastLoadGameTitle, lastLoadGameUseList);
\r
8664 return LoadGame(lastLoadGameFP, gameNumber,
\r
8665 lastLoadGameTitle, lastLoadGameUseList);
\r
8671 /* Load the nth game from open file f */
\r
8673 LoadGame(f, gameNumber, title, useList)
\r
8680 char buf[MSG_SIZ];
\r
8681 int gn = gameNumber;
\r
8682 ListGame *lg = NULL;
\r
8683 int numPGNTags = 0;
\r
8685 GameMode oldGameMode;
\r
8686 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8688 if (appData.debugMode)
\r
8689 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8691 if (gameMode == Training )
\r
8692 SetTrainingModeOff();
\r
8694 oldGameMode = gameMode;
\r
8695 if (gameMode != BeginningOfGame) {
\r
8696 Reset(FALSE, TRUE);
\r
8700 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8701 fclose(lastLoadGameFP);
\r
8705 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8708 fseek(f, lg->offset, 0);
\r
8709 GameListHighlight(gameNumber);
\r
8713 DisplayError(_("Game number out of range"), 0);
\r
8717 GameListDestroy();
\r
8718 if (fseek(f, 0, 0) == -1) {
\r
8719 if (f == lastLoadGameFP ?
\r
8720 gameNumber == lastLoadGameNumber + 1 :
\r
8721 gameNumber == 1) {
\r
8724 DisplayError(_("Can't seek on game file"), 0);
\r
8729 lastLoadGameFP = f;
\r
8730 lastLoadGameNumber = gameNumber;
\r
8731 strcpy(lastLoadGameTitle, title);
\r
8732 lastLoadGameUseList = useList;
\r
8736 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8737 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8738 lg->gameInfo.black);
\r
8739 DisplayTitle(buf);
\r
8740 } else if (*title != NULLCHAR) {
\r
8741 if (gameNumber > 1) {
\r
8742 sprintf(buf, "%s %d", title, gameNumber);
\r
8743 DisplayTitle(buf);
\r
8745 DisplayTitle(title);
\r
8749 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8750 gameMode = PlayFromGameFile;
\r
8754 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8755 CopyBoard(boards[0], initialPosition);
\r
8759 * Skip the first gn-1 games in the file.
\r
8760 * Also skip over anything that precedes an identifiable
\r
8761 * start of game marker, to avoid being confused by
\r
8762 * garbage at the start of the file. Currently
\r
8763 * recognized start of game markers are the move number "1",
\r
8764 * the pattern "gnuchess .* game", the pattern
\r
8765 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8766 * A game that starts with one of the latter two patterns
\r
8767 * will also have a move number 1, possibly
\r
8768 * following a position diagram.
\r
8769 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8770 * start with an unnumbered move. Does that break anything?
\r
8772 cm = lastLoadGameStart = (ChessMove) 0;
\r
8774 yyboardindex = forwardMostMove;
\r
8775 cm = (ChessMove) yylex();
\r
8777 case (ChessMove) 0:
\r
8778 if (cmailMsgLoaded) {
\r
8779 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8781 Reset(TRUE, TRUE);
\r
8782 DisplayError(_("Game not found in file"), 0);
\r
8786 case GNUChessGame:
\r
8789 lastLoadGameStart = cm;
\r
8792 case MoveNumberOne:
\r
8793 switch (lastLoadGameStart) {
\r
8794 case GNUChessGame:
\r
8798 case MoveNumberOne:
\r
8799 case (ChessMove) 0:
\r
8800 gn--; /* count this game */
\r
8801 lastLoadGameStart = cm;
\r
8810 switch (lastLoadGameStart) {
\r
8811 case GNUChessGame:
\r
8813 case MoveNumberOne:
\r
8814 case (ChessMove) 0:
\r
8815 gn--; /* count this game */
\r
8816 lastLoadGameStart = cm;
\r
8819 lastLoadGameStart = cm; /* game counted already */
\r
8827 yyboardindex = forwardMostMove;
\r
8828 cm = (ChessMove) yylex();
\r
8829 } while (cm == PGNTag || cm == Comment);
\r
8836 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8837 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8838 != CMAIL_OLD_RESULT) {
\r
8839 nCmailResults ++ ;
\r
8840 cmailResult[ CMAIL_MAX_GAMES
\r
8841 - gn - 1] = CMAIL_OLD_RESULT;
\r
8847 /* Only a NormalMove can be at the start of a game
\r
8848 * without a position diagram. */
\r
8849 if (lastLoadGameStart == (ChessMove) 0) {
\r
8851 lastLoadGameStart = MoveNumberOne;
\r
8860 if (appData.debugMode)
\r
8861 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8863 if (cm == XBoardGame) {
\r
8864 /* Skip any header junk before position diagram and/or move 1 */
\r
8866 yyboardindex = forwardMostMove;
\r
8867 cm = (ChessMove) yylex();
\r
8869 if (cm == (ChessMove) 0 ||
\r
8870 cm == GNUChessGame || cm == XBoardGame) {
\r
8871 /* Empty game; pretend end-of-file and handle later */
\r
8872 cm = (ChessMove) 0;
\r
8876 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8877 cm == PGNTag || cm == Comment)
\r
8880 } else if (cm == GNUChessGame) {
\r
8881 if (gameInfo.event != NULL) {
\r
8882 free(gameInfo.event);
\r
8884 gameInfo.event = StrSave(yy_text);
\r
8887 startedFromSetupPosition = FALSE;
\r
8888 while (cm == PGNTag) {
\r
8889 if (appData.debugMode)
\r
8890 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8891 err = ParsePGNTag(yy_text, &gameInfo);
\r
8892 if (!err) numPGNTags++;
\r
8894 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8895 if(gameInfo.variant != oldVariant) {
\r
8896 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8897 InitPosition(TRUE);
\r
8898 oldVariant = gameInfo.variant;
\r
8899 if (appData.debugMode)
\r
8900 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8904 if (gameInfo.fen != NULL) {
\r
8905 Board initial_position;
\r
8906 startedFromSetupPosition = TRUE;
\r
8907 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8908 Reset(TRUE, TRUE);
\r
8909 DisplayError(_("Bad FEN position in file"), 0);
\r
8912 CopyBoard(boards[0], initial_position);
\r
8913 if (blackPlaysFirst) {
\r
8914 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8915 CopyBoard(boards[1], initial_position);
\r
8916 strcpy(moveList[0], "");
\r
8917 strcpy(parseList[0], "");
\r
8918 timeRemaining[0][1] = whiteTimeRemaining;
\r
8919 timeRemaining[1][1] = blackTimeRemaining;
\r
8920 if (commentList[0] != NULL) {
\r
8921 commentList[1] = commentList[0];
\r
8922 commentList[0] = NULL;
\r
8925 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8927 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8929 initialRulePlies = FENrulePlies;
\r
8930 epStatus[forwardMostMove] = FENepStatus;
\r
8931 for( i=0; i< nrCastlingRights; i++ )
\r
8932 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8934 yyboardindex = forwardMostMove;
\r
8935 free(gameInfo.fen);
\r
8936 gameInfo.fen = NULL;
\r
8939 yyboardindex = forwardMostMove;
\r
8940 cm = (ChessMove) yylex();
\r
8942 /* Handle comments interspersed among the tags */
\r
8943 while (cm == Comment) {
\r
8945 if (appData.debugMode)
\r
8946 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8948 if (*p == '{' || *p == '[' || *p == '(') {
\r
8949 p[strlen(p) - 1] = NULLCHAR;
\r
8952 while (*p == '\n') p++;
\r
8953 AppendComment(currentMove, p);
\r
8954 yyboardindex = forwardMostMove;
\r
8955 cm = (ChessMove) yylex();
\r
8959 /* don't rely on existence of Event tag since if game was
\r
8960 * pasted from clipboard the Event tag may not exist
\r
8962 if (numPGNTags > 0){
\r
8964 if (gameInfo.variant == VariantNormal) {
\r
8965 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8968 if( appData.autoDisplayTags ) {
\r
8969 tags = PGNTags(&gameInfo);
\r
8970 TagsPopUp(tags, CmailMsg());
\r
8975 /* Make something up, but don't display it now */
\r
8980 if (cm == PositionDiagram) {
\r
8983 Board initial_position;
\r
8985 if (appData.debugMode)
\r
8986 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
8988 if (!startedFromSetupPosition) {
\r
8990 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
8991 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9001 initial_position[i][j++] = CharToPiece(*p);
\r
9004 while (*p == ' ' || *p == '\t' ||
\r
9005 *p == '\n' || *p == '\r') p++;
\r
9007 if (strncmp(p, "black", strlen("black"))==0)
\r
9008 blackPlaysFirst = TRUE;
\r
9010 blackPlaysFirst = FALSE;
\r
9011 startedFromSetupPosition = TRUE;
\r
9013 CopyBoard(boards[0], initial_position);
\r
9014 if (blackPlaysFirst) {
\r
9015 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9016 CopyBoard(boards[1], initial_position);
\r
9017 strcpy(moveList[0], "");
\r
9018 strcpy(parseList[0], "");
\r
9019 timeRemaining[0][1] = whiteTimeRemaining;
\r
9020 timeRemaining[1][1] = blackTimeRemaining;
\r
9021 if (commentList[0] != NULL) {
\r
9022 commentList[1] = commentList[0];
\r
9023 commentList[0] = NULL;
\r
9026 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9029 yyboardindex = forwardMostMove;
\r
9030 cm = (ChessMove) yylex();
\r
9033 if (first.pr == NoProc) {
\r
9034 StartChessProgram(&first);
\r
9036 InitChessProgram(&first, FALSE);
\r
9037 SendToProgram("force\n", &first);
\r
9038 if (startedFromSetupPosition) {
\r
9039 SendBoard(&first, forwardMostMove);
\r
9040 if (appData.debugMode) {
\r
9041 fprintf(debugFP, "Load Game\n");
\r
9043 DisplayBothClocks();
\r
9046 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9047 loadFlag = appData.suppressLoadMoves;
\r
9049 while (cm == Comment) {
\r
9051 if (appData.debugMode)
\r
9052 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9054 if (*p == '{' || *p == '[' || *p == '(') {
\r
9055 p[strlen(p) - 1] = NULLCHAR;
\r
9058 while (*p == '\n') p++;
\r
9059 AppendComment(currentMove, p);
\r
9060 yyboardindex = forwardMostMove;
\r
9061 cm = (ChessMove) yylex();
\r
9064 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9065 cm == WhiteWins || cm == BlackWins ||
\r
9066 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9067 DisplayMessage("", _("No moves in game"));
\r
9068 if (cmailMsgLoaded) {
\r
9069 if (appData.debugMode)
\r
9070 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9071 ClearHighlights();
\r
9074 DrawPosition(FALSE, boards[currentMove]);
\r
9075 DisplayBothClocks();
\r
9076 gameMode = EditGame;
\r
9078 gameFileFP = NULL;
\r
9083 // [HGM] PV info: routine tests if comment empty
\r
9084 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9085 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9087 if (!matchMode && appData.timeDelay != 0)
\r
9088 DrawPosition(FALSE, boards[currentMove]);
\r
9090 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9091 programStats.ok_to_send = 1;
\r
9094 /* if the first token after the PGN tags is a move
\r
9095 * and not move number 1, retrieve it from the parser
\r
9097 if (cm != MoveNumberOne)
\r
9098 LoadGameOneMove(cm);
\r
9100 /* load the remaining moves from the file */
\r
9101 while (LoadGameOneMove((ChessMove)0)) {
\r
9102 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9103 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9106 /* rewind to the start of the game */
\r
9107 currentMove = backwardMostMove;
\r
9109 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9111 if (oldGameMode == AnalyzeFile ||
\r
9112 oldGameMode == AnalyzeMode) {
\r
9113 AnalyzeFileEvent();
\r
9116 if (matchMode || appData.timeDelay == 0) {
\r
9118 gameMode = EditGame;
\r
9120 } else if (appData.timeDelay > 0) {
\r
9121 AutoPlayGameLoop();
\r
9124 if (appData.debugMode)
\r
9125 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9127 loadFlag = 0; /* [HGM] true game starts */
\r
9131 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9133 ReloadPosition(offset)
\r
9136 int positionNumber = lastLoadPositionNumber + offset;
\r
9137 if (lastLoadPositionFP == NULL) {
\r
9138 DisplayError(_("No position has been loaded yet"), 0);
\r
9141 if (positionNumber <= 0) {
\r
9142 DisplayError(_("Can't back up any further"), 0);
\r
9145 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9146 lastLoadPositionTitle);
\r
9149 /* Load the nth position from the given file */
\r
9151 LoadPositionFromFile(filename, n, title)
\r
9157 char buf[MSG_SIZ];
\r
9159 if (strcmp(filename, "-") == 0) {
\r
9160 return LoadPosition(stdin, n, "stdin");
\r
9162 f = fopen(filename, "rb");
\r
9164 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9165 DisplayError(buf, errno);
\r
9168 return LoadPosition(f, n, title);
\r
9173 /* Load the nth position from the given open file, and close it */
\r
9175 LoadPosition(f, positionNumber, title)
\r
9177 int positionNumber;
\r
9180 char *p, line[MSG_SIZ];
\r
9181 Board initial_position;
\r
9182 int i, j, fenMode, pn;
\r
9184 if (gameMode == Training )
\r
9185 SetTrainingModeOff();
\r
9187 if (gameMode != BeginningOfGame) {
\r
9188 Reset(FALSE, TRUE);
\r
9190 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9191 fclose(lastLoadPositionFP);
\r
9193 if (positionNumber == 0) positionNumber = 1;
\r
9194 lastLoadPositionFP = f;
\r
9195 lastLoadPositionNumber = positionNumber;
\r
9196 strcpy(lastLoadPositionTitle, title);
\r
9197 if (first.pr == NoProc) {
\r
9198 StartChessProgram(&first);
\r
9199 InitChessProgram(&first, FALSE);
\r
9201 pn = positionNumber;
\r
9202 if (positionNumber < 0) {
\r
9203 /* Negative position number means to seek to that byte offset */
\r
9204 if (fseek(f, -positionNumber, 0) == -1) {
\r
9205 DisplayError(_("Can't seek on position file"), 0);
\r
9210 if (fseek(f, 0, 0) == -1) {
\r
9211 if (f == lastLoadPositionFP ?
\r
9212 positionNumber == lastLoadPositionNumber + 1 :
\r
9213 positionNumber == 1) {
\r
9216 DisplayError(_("Can't seek on position file"), 0);
\r
9221 /* See if this file is FEN or old-style xboard */
\r
9222 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9223 DisplayError(_("Position not found in file"), 0);
\r
9227 switch (line[0]) {
\r
9228 case '#': case 'x':
\r
9232 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9233 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9234 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9235 case '7': case '8': case '9':
\r
9236 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9237 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9238 case 'C': case 'W': case 'c': case 'w':
\r
9243 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9244 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9248 if (fenMode || line[0] == '#') pn--;
\r
9250 /* skip positions before number pn */
\r
9251 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9252 Reset(TRUE, TRUE);
\r
9253 DisplayError(_("Position not found in file"), 0);
\r
9256 if (fenMode || line[0] == '#') pn--;
\r
9261 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9262 DisplayError(_("Bad FEN position in file"), 0);
\r
9266 (void) fgets(line, MSG_SIZ, f);
\r
9267 (void) fgets(line, MSG_SIZ, f);
\r
9269 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9270 (void) fgets(line, MSG_SIZ, f);
\r
9271 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9274 initial_position[i][j++] = CharToPiece(*p);
\r
9278 blackPlaysFirst = FALSE;
\r
9280 (void) fgets(line, MSG_SIZ, f);
\r
9281 if (strncmp(line, "black", strlen("black"))==0)
\r
9282 blackPlaysFirst = TRUE;
\r
9285 startedFromSetupPosition = TRUE;
\r
9287 SendToProgram("force\n", &first);
\r
9288 CopyBoard(boards[0], initial_position);
\r
9289 if (blackPlaysFirst) {
\r
9290 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9291 strcpy(moveList[0], "");
\r
9292 strcpy(parseList[0], "");
\r
9293 CopyBoard(boards[1], initial_position);
\r
9294 DisplayMessage("", _("Black to play"));
\r
9296 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9297 DisplayMessage("", _("White to play"));
\r
9299 /* [HGM] copy FEN attributes as well */
\r
9301 initialRulePlies = FENrulePlies;
\r
9302 epStatus[forwardMostMove] = FENepStatus;
\r
9303 for( i=0; i< nrCastlingRights; i++ )
\r
9304 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9306 SendBoard(&first, forwardMostMove);
\r
9307 if (appData.debugMode) {
\r
9309 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9310 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9311 fprintf(debugFP, "Load Position\n");
\r
9314 if (positionNumber > 1) {
\r
9315 sprintf(line, "%s %d", title, positionNumber);
\r
9316 DisplayTitle(line);
\r
9318 DisplayTitle(title);
\r
9320 gameMode = EditGame;
\r
9323 timeRemaining[0][1] = whiteTimeRemaining;
\r
9324 timeRemaining[1][1] = blackTimeRemaining;
\r
9325 DrawPosition(FALSE, boards[currentMove]);
\r
9332 CopyPlayerNameIntoFileName(dest, src)
\r
9333 char **dest, *src;
\r
9335 while (*src != NULLCHAR && *src != ',') {
\r
9336 if (*src == ' ') {
\r
9340 *(*dest)++ = *src++;
\r
9345 char *DefaultFileName(ext)
\r
9348 static char def[MSG_SIZ];
\r
9351 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9353 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9355 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9359 def[0] = NULLCHAR;
\r
9364 /* Save the current game to the given file */
\r
9366 SaveGameToFile(filename, append)
\r
9371 char buf[MSG_SIZ];
\r
9373 if (strcmp(filename, "-") == 0) {
\r
9374 return SaveGame(stdout, 0, NULL);
\r
9376 f = fopen(filename, append ? "a" : "w");
\r
9378 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9379 DisplayError(buf, errno);
\r
9382 return SaveGame(f, 0, NULL);
\r
9391 static char buf[MSG_SIZ];
\r
9394 p = strchr(str, ' ');
\r
9395 if (p == NULL) return str;
\r
9396 strncpy(buf, str, p - str);
\r
9397 buf[p - str] = NULLCHAR;
\r
9401 #define PGN_MAX_LINE 75
\r
9403 #define PGN_SIDE_WHITE 0
\r
9404 #define PGN_SIDE_BLACK 1
\r
9407 static int FindFirstMoveOutOfBook( int side )
\r
9411 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9412 int index = backwardMostMove;
\r
9413 int has_book_hit = 0;
\r
9415 if( (index % 2) != side ) {
\r
9419 while( index < forwardMostMove ) {
\r
9420 /* Check to see if engine is in book */
\r
9421 int depth = pvInfoList[index].depth;
\r
9422 int score = pvInfoList[index].score;
\r
9425 if( depth <= 2 ) {
\r
9428 else if( score == 0 && depth == 63 ) {
\r
9429 in_book = 1; /* Zappa */
\r
9431 else if( score == 2 && depth == 99 ) {
\r
9432 in_book = 1; /* Abrok */
\r
9435 has_book_hit += in_book;
\r
9451 void GetOutOfBookInfo( char * buf )
\r
9455 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9457 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9458 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9462 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9463 for( i=0; i<2; i++ ) {
\r
9467 if( i > 0 && oob[0] >= 0 ) {
\r
9468 strcat( buf, " " );
\r
9471 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9472 sprintf( buf+strlen(buf), "%s%.2f",
\r
9473 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9474 pvInfoList[idx].score / 100.0 );
\r
9480 /* Save game in PGN style and close the file */
\r
9485 int i, offset, linelen, newblock;
\r
9487 // char *movetext;
\r
9489 int movelen, numlen, blank;
\r
9490 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9492 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9494 tm = time((time_t *) NULL);
\r
9496 PrintPGNTags(f, &gameInfo);
\r
9498 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9499 char *fen = PositionToFEN(backwardMostMove, NULL);
\r
9500 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9501 fprintf(f, "\n{--------------\n");
\r
9502 PrintPosition(f, backwardMostMove);
\r
9503 fprintf(f, "--------------}\n");
\r
9507 /* [AS] Out of book annotation */
\r
9508 if( appData.saveOutOfBookInfo ) {
\r
9511 GetOutOfBookInfo( buf );
\r
9513 if( buf[0] != '\0' ) {
\r
9514 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9521 i = backwardMostMove;
\r
9525 while (i < forwardMostMove) {
\r
9526 /* Print comments preceding this move */
\r
9527 if (commentList[i] != NULL) {
\r
9528 if (linelen > 0) fprintf(f, "\n");
\r
9529 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9534 /* Format move number */
\r
9535 if ((i % 2) == 0) {
\r
9536 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9539 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9541 numtext[0] = NULLCHAR;
\r
9544 numlen = strlen(numtext);
\r
9547 /* Print move number */
\r
9548 blank = linelen > 0 && numlen > 0;
\r
9549 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9558 fprintf(f, numtext);
\r
9559 linelen += numlen;
\r
9562 strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
\r
9563 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
\r
9564 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9565 int p = movelen - 1;
\r
9566 if(move_buffer[p] == ' ') p--;
\r
9567 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
\r
9568 while(p && move_buffer[--p] != '(');
\r
9569 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
\r
9574 blank = linelen > 0 && movelen > 0;
\r
9575 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9584 fprintf(f, move_buffer);
\r
9585 linelen += movelen;
\r
9587 /* [AS] Add PV info if present */
\r
9588 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9589 /* [HGM] add time */
\r
9590 char buf[MSG_SIZ]; int seconds = 0;
\r
9593 if(i >= backwardMostMove) {
\r
9594 if(WhiteOnMove(i))
\r
9595 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9596 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
\r
9598 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9599 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
\r
9601 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9603 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9606 if( seconds <= 0) buf[0] = 0; else
\r
9607 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9608 seconds = (seconds + 4)/10; // round to full seconds
\r
9609 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9610 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9613 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9614 pvInfoList[i].score >= 0 ? "+" : "",
\r
9615 pvInfoList[i].score / 100.0,
\r
9616 pvInfoList[i].depth,
\r
9619 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9621 /* Print score/depth */
\r
9622 blank = linelen > 0 && movelen > 0;
\r
9623 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9632 fprintf(f, move_buffer);
\r
9633 linelen += movelen;
\r
9639 /* Start a new line */
\r
9640 if (linelen > 0) fprintf(f, "\n");
\r
9642 /* Print comments after last move */
\r
9643 if (commentList[i] != NULL) {
\r
9644 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9647 /* Print result */
\r
9648 if (gameInfo.resultDetails != NULL &&
\r
9649 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9650 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9651 PGNResult(gameInfo.result));
\r
9653 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9660 /* Save game in old style and close the file */
\r
9662 SaveGameOldStyle(f)
\r
9668 tm = time((time_t *) NULL);
\r
9670 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9671 PrintOpponents(f);
\r
9673 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9674 fprintf(f, "\n[--------------\n");
\r
9675 PrintPosition(f, backwardMostMove);
\r
9676 fprintf(f, "--------------]\n");
\r
9681 i = backwardMostMove;
\r
9682 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9684 while (i < forwardMostMove) {
\r
9685 if (commentList[i] != NULL) {
\r
9686 fprintf(f, "[%s]\n", commentList[i]);
\r
9689 if ((i % 2) == 1) {
\r
9690 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9693 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9695 if (commentList[i] != NULL) {
\r
9699 if (i >= forwardMostMove) {
\r
9703 fprintf(f, "%s\n", parseList[i]);
\r
9708 if (commentList[i] != NULL) {
\r
9709 fprintf(f, "[%s]\n", commentList[i]);
\r
9712 /* This isn't really the old style, but it's close enough */
\r
9713 if (gameInfo.resultDetails != NULL &&
\r
9714 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9715 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9716 gameInfo.resultDetails);
\r
9718 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9725 /* Save the current game to open file f and close the file */
\r
9727 SaveGame(f, dummy, dummy2)
\r
9732 if (gameMode == EditPosition) EditPositionDone();
\r
9733 if (appData.oldSaveStyle)
\r
9734 return SaveGameOldStyle(f);
\r
9736 return SaveGamePGN(f);
\r
9739 /* Save the current position to the given file */
\r
9741 SavePositionToFile(filename)
\r
9745 char buf[MSG_SIZ];
\r
9747 if (strcmp(filename, "-") == 0) {
\r
9748 return SavePosition(stdout, 0, NULL);
\r
9750 f = fopen(filename, "a");
\r
9752 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9753 DisplayError(buf, errno);
\r
9756 SavePosition(f, 0, NULL);
\r
9762 /* Save the current position to the given open file and close the file */
\r
9764 SavePosition(f, dummy, dummy2)
\r
9772 if (appData.oldSaveStyle) {
\r
9773 tm = time((time_t *) NULL);
\r
9775 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9776 PrintOpponents(f);
\r
9777 fprintf(f, "[--------------\n");
\r
9778 PrintPosition(f, currentMove);
\r
9779 fprintf(f, "--------------]\n");
\r
9781 fen = PositionToFEN(currentMove, NULL);
\r
9782 fprintf(f, "%s\n", fen);
\r
9790 ReloadCmailMsgEvent(unregister)
\r
9794 static char *inFilename = NULL;
\r
9795 static char *outFilename;
\r
9797 struct stat inbuf, outbuf;
\r
9800 /* Any registered moves are unregistered if unregister is set, */
\r
9801 /* i.e. invoked by the signal handler */
\r
9803 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9804 cmailMoveRegistered[i] = FALSE;
\r
9805 if (cmailCommentList[i] != NULL) {
\r
9806 free(cmailCommentList[i]);
\r
9807 cmailCommentList[i] = NULL;
\r
9810 nCmailMovesRegistered = 0;
\r
9813 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9814 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9816 nCmailResults = 0;
\r
9818 if (inFilename == NULL) {
\r
9819 /* Because the filenames are static they only get malloced once */
\r
9820 /* and they never get freed */
\r
9821 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9822 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9824 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9825 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9828 status = stat(outFilename, &outbuf);
\r
9830 cmailMailedMove = FALSE;
\r
9832 status = stat(inFilename, &inbuf);
\r
9833 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9836 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9837 counts the games, notes how each one terminated, etc.
\r
9839 It would be nice to remove this kludge and instead gather all
\r
9840 the information while building the game list. (And to keep it
\r
9841 in the game list nodes instead of having a bunch of fixed-size
\r
9842 parallel arrays.) Note this will require getting each game's
\r
9843 termination from the PGN tags, as the game list builder does
\r
9844 not process the game moves. --mann
\r
9846 cmailMsgLoaded = TRUE;
\r
9847 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9849 /* Load first game in the file or popup game menu */
\r
9850 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9852 #endif /* !WIN32 */
\r
9860 char string[MSG_SIZ];
\r
9862 if ( cmailMailedMove
\r
9863 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9864 return TRUE; /* Allow free viewing */
\r
9867 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9868 /* with the move registered when the conditions for registering no */
\r
9870 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9871 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9872 nCmailMovesRegistered --;
\r
9874 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9876 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9877 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9881 if (cmailOldMove == -1) {
\r
9882 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9886 if (currentMove > cmailOldMove + 1) {
\r
9887 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9891 if (currentMove < cmailOldMove) {
\r
9892 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9896 if (forwardMostMove > currentMove) {
\r
9897 /* Silently truncate extra moves */
\r
9901 if ( (currentMove == cmailOldMove + 1)
\r
9902 || ( (currentMove == cmailOldMove)
\r
9903 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9904 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9905 if (gameInfo.result != GameUnfinished) {
\r
9906 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9909 if (commentList[currentMove] != NULL) {
\r
9910 cmailCommentList[lastLoadGameNumber - 1]
\r
9911 = StrSave(commentList[currentMove]);
\r
9913 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9915 if (appData.debugMode)
\r
9916 fprintf(debugFP, "Saving %s for game %d\n",
\r
9917 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9920 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9922 f = fopen(string, "w");
\r
9923 if (appData.oldSaveStyle) {
\r
9924 SaveGameOldStyle(f); /* also closes the file */
\r
9926 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9927 f = fopen(string, "w");
\r
9928 SavePosition(f, 0, NULL); /* also closes the file */
\r
9930 fprintf(f, "{--------------\n");
\r
9931 PrintPosition(f, currentMove);
\r
9932 fprintf(f, "--------------}\n\n");
\r
9934 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9937 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9938 nCmailMovesRegistered ++;
\r
9939 } else if (nCmailGames == 1) {
\r
9940 DisplayError(_("You have not made a move yet"), 0);
\r
9951 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9952 FILE *commandOutput;
\r
9953 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9954 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9960 if (! cmailMsgLoaded) {
\r
9961 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9965 if (nCmailGames == nCmailResults) {
\r
9966 DisplayError(_("No unfinished games"), 0);
\r
9970 #if CMAIL_PROHIBIT_REMAIL
\r
9971 if (cmailMailedMove) {
\r
9972 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
9973 DisplayError(msg, 0);
\r
9978 if (! (cmailMailedMove || RegisterMove())) return;
\r
9980 if ( cmailMailedMove
\r
9981 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9982 sprintf(string, partCommandString,
\r
9983 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9984 commandOutput = popen(string, "r");
\r
9986 if (commandOutput == NULL) {
\r
9987 DisplayError(_("Failed to invoke cmail"), 0);
\r
9989 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9990 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
9992 if (nBuffers > 1) {
\r
9993 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
9994 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
9995 nBytes = MSG_SIZ - 1;
\r
9997 (void) memcpy(msg, buffer, nBytes);
\r
9999 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10001 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10002 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10005 for (i = 0; i < nCmailGames; i ++) {
\r
10006 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10007 archived = FALSE;
\r
10011 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10013 sprintf(buffer, "%s/%s.%s.archive",
\r
10015 appData.cmailGameName,
\r
10017 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10018 cmailMsgLoaded = FALSE;
\r
10022 DisplayInformation(msg);
\r
10023 pclose(commandOutput);
\r
10026 if ((*cmailMsg) != '\0') {
\r
10027 DisplayInformation(cmailMsg);
\r
10032 #endif /* !WIN32 */
\r
10041 int prependComma = 0;
\r
10043 char string[MSG_SIZ]; /* Space for game-list */
\r
10046 if (!cmailMsgLoaded) return "";
\r
10048 if (cmailMailedMove) {
\r
10049 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10051 /* Create a list of games left */
\r
10052 sprintf(string, "[");
\r
10053 for (i = 0; i < nCmailGames; i ++) {
\r
10054 if (! ( cmailMoveRegistered[i]
\r
10055 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10056 if (prependComma) {
\r
10057 sprintf(number, ",%d", i + 1);
\r
10059 sprintf(number, "%d", i + 1);
\r
10060 prependComma = 1;
\r
10063 strcat(string, number);
\r
10066 strcat(string, "]");
\r
10068 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10069 switch (nCmailGames) {
\r
10071 sprintf(cmailMsg,
\r
10072 _("Still need to make move for game\n"));
\r
10076 sprintf(cmailMsg,
\r
10077 _("Still need to make moves for both games\n"));
\r
10081 sprintf(cmailMsg,
\r
10082 _("Still need to make moves for all %d games\n"),
\r
10087 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10089 sprintf(cmailMsg,
\r
10090 _("Still need to make a move for game %s\n"),
\r
10095 if (nCmailResults == nCmailGames) {
\r
10096 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10098 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10103 sprintf(cmailMsg,
\r
10104 _("Still need to make moves for games %s\n"),
\r
10110 #endif /* WIN32 */
\r
10116 if (gameMode == Training)
\r
10117 SetTrainingModeOff();
\r
10119 Reset(TRUE, TRUE);
\r
10120 cmailMsgLoaded = FALSE;
\r
10121 if (appData.icsActive) {
\r
10122 SendToICS(ics_prefix);
\r
10123 SendToICS("refresh\n");
\r
10128 ExitEvent(status)
\r
10132 if (exiting > 2) {
\r
10133 /* Give up on clean exit */
\r
10136 if (exiting > 1) {
\r
10137 /* Keep trying for clean exit */
\r
10141 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10143 if (telnetISR != NULL) {
\r
10144 RemoveInputSource(telnetISR);
\r
10146 if (icsPR != NoProc) {
\r
10147 DestroyChildProcess(icsPR, TRUE);
\r
10150 /* Save game if resource set and not already saved by GameEnds() */
\r
10151 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10152 && forwardMostMove > 0) {
\r
10153 if (*appData.saveGameFile != NULLCHAR) {
\r
10154 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10155 } else if (appData.autoSaveGames) {
\r
10158 if (*appData.savePositionFile != NULLCHAR) {
\r
10159 SavePositionToFile(appData.savePositionFile);
\r
10162 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10164 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10165 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10167 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10168 /* make sure this other one finishes before killing it! */
\r
10169 if(endingGame) { int count = 0;
\r
10170 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10171 while(endingGame && count++ < 10) DoSleep(1);
\r
10172 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10175 /* Kill off chess programs */
\r
10176 if (first.pr != NoProc) {
\r
10177 ExitAnalyzeMode();
\r
10179 DoSleep( appData.delayBeforeQuit );
\r
10180 SendToProgram("quit\n", &first);
\r
10181 DoSleep( appData.delayAfterQuit );
\r
10182 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10184 if (second.pr != NoProc) {
\r
10185 DoSleep( appData.delayBeforeQuit );
\r
10186 SendToProgram("quit\n", &second);
\r
10187 DoSleep( appData.delayAfterQuit );
\r
10188 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10190 if (first.isr != NULL) {
\r
10191 RemoveInputSource(first.isr);
\r
10193 if (second.isr != NULL) {
\r
10194 RemoveInputSource(second.isr);
\r
10197 ShutDownFrontEnd();
\r
10204 if (appData.debugMode)
\r
10205 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10209 if (gameMode == MachinePlaysWhite ||
\r
10210 gameMode == MachinePlaysBlack) {
\r
10213 DisplayBothClocks();
\r
10215 if (gameMode == PlayFromGameFile) {
\r
10216 if (appData.timeDelay >= 0)
\r
10217 AutoPlayGameLoop();
\r
10218 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10219 Reset(FALSE, TRUE);
\r
10220 SendToICS(ics_prefix);
\r
10221 SendToICS("refresh\n");
\r
10222 } else if (currentMove < forwardMostMove) {
\r
10223 ForwardInner(forwardMostMove);
\r
10225 pauseExamInvalid = FALSE;
\r
10227 switch (gameMode) {
\r
10230 case IcsExamining:
\r
10231 pauseExamForwardMostMove = forwardMostMove;
\r
10232 pauseExamInvalid = FALSE;
\r
10233 /* fall through */
\r
10234 case IcsObserving:
\r
10235 case IcsPlayingWhite:
\r
10236 case IcsPlayingBlack:
\r
10240 case PlayFromGameFile:
\r
10241 (void) StopLoadGameTimer();
\r
10245 case BeginningOfGame:
\r
10246 if (appData.icsActive) return;
\r
10247 /* else fall through */
\r
10248 case MachinePlaysWhite:
\r
10249 case MachinePlaysBlack:
\r
10250 case TwoMachinesPlay:
\r
10251 if (forwardMostMove == 0)
\r
10252 return; /* don't pause if no one has moved */
\r
10253 if ((gameMode == MachinePlaysWhite &&
\r
10254 !WhiteOnMove(forwardMostMove)) ||
\r
10255 (gameMode == MachinePlaysBlack &&
\r
10256 WhiteOnMove(forwardMostMove))) {
\r
10267 EditCommentEvent()
\r
10269 char title[MSG_SIZ];
\r
10271 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10272 strcpy(title, _("Edit comment"));
\r
10274 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10275 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10276 parseList[currentMove - 1]);
\r
10279 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10286 char *tags = PGNTags(&gameInfo);
\r
10287 EditTagsPopUp(tags);
\r
10292 AnalyzeModeEvent()
\r
10294 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10297 if (gameMode != AnalyzeFile) {
\r
10298 if (!appData.icsEngineAnalyze) {
\r
10300 if (gameMode != EditGame) return;
\r
10302 ResurrectChessProgram();
\r
10303 SendToProgram("analyze\n", &first);
\r
10304 first.analyzing = TRUE;
\r
10305 /*first.maybeThinking = TRUE;*/
\r
10306 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10307 AnalysisPopUp(_("Analysis"),
\r
10308 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10310 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10315 StartAnalysisClock();
\r
10316 GetTimeMark(&lastNodeCountTime);
\r
10317 lastNodeCount = 0;
\r
10321 AnalyzeFileEvent()
\r
10323 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10326 if (gameMode != AnalyzeMode) {
\r
10328 if (gameMode != EditGame) return;
\r
10329 ResurrectChessProgram();
\r
10330 SendToProgram("analyze\n", &first);
\r
10331 first.analyzing = TRUE;
\r
10332 /*first.maybeThinking = TRUE;*/
\r
10333 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10334 AnalysisPopUp(_("Analysis"),
\r
10335 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10337 gameMode = AnalyzeFile;
\r
10342 StartAnalysisClock();
\r
10343 GetTimeMark(&lastNodeCountTime);
\r
10344 lastNodeCount = 0;
\r
10348 MachineWhiteEvent()
\r
10350 char buf[MSG_SIZ];
\r
10351 char *bookHit = NULL;
\r
10353 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10357 if (gameMode == PlayFromGameFile ||
\r
10358 gameMode == TwoMachinesPlay ||
\r
10359 gameMode == Training ||
\r
10360 gameMode == AnalyzeMode ||
\r
10361 gameMode == EndOfGame)
\r
10364 if (gameMode == EditPosition)
\r
10365 EditPositionDone();
\r
10367 if (!WhiteOnMove(currentMove)) {
\r
10368 DisplayError(_("It is not White's turn"), 0);
\r
10372 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10373 ExitAnalyzeMode();
\r
10375 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10376 gameMode == AnalyzeFile)
\r
10379 ResurrectChessProgram(); /* in case it isn't running */
\r
10380 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10381 gameMode = MachinePlaysWhite;
\r
10384 gameMode = MachinePlaysWhite;
\r
10388 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10389 DisplayTitle(buf);
\r
10390 if (first.sendName) {
\r
10391 sprintf(buf, "name %s\n", gameInfo.black);
\r
10392 SendToProgram(buf, &first);
\r
10394 if (first.sendTime) {
\r
10395 if (first.useColors) {
\r
10396 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10398 SendTimeRemaining(&first, TRUE);
\r
10400 if (first.useColors) {
\r
10401 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10403 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10404 SetMachineThinkingEnables();
\r
10405 first.maybeThinking = TRUE;
\r
10408 if (appData.autoFlipView && !flipView) {
\r
10409 flipView = !flipView;
\r
10410 DrawPosition(FALSE, NULL);
\r
10411 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10414 if(bookHit) { // [HGM] book: simulate book reply
\r
10415 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10417 programStats.nodes = programStats.depth = programStats.time =
\r
10418 programStats.score = programStats.got_only_move = 0;
\r
10419 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10421 strcpy(bookMove, "move ");
\r
10422 strcat(bookMove, bookHit);
\r
10423 HandleMachineMove(bookMove, &first);
\r
10428 MachineBlackEvent()
\r
10430 char buf[MSG_SIZ];
\r
10431 char *bookHit = NULL;
\r
10433 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10437 if (gameMode == PlayFromGameFile ||
\r
10438 gameMode == TwoMachinesPlay ||
\r
10439 gameMode == Training ||
\r
10440 gameMode == AnalyzeMode ||
\r
10441 gameMode == EndOfGame)
\r
10444 if (gameMode == EditPosition)
\r
10445 EditPositionDone();
\r
10447 if (WhiteOnMove(currentMove)) {
\r
10448 DisplayError(_("It is not Black's turn"), 0);
\r
10452 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10453 ExitAnalyzeMode();
\r
10455 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10456 gameMode == AnalyzeFile)
\r
10459 ResurrectChessProgram(); /* in case it isn't running */
\r
10460 gameMode = MachinePlaysBlack;
\r
10464 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10465 DisplayTitle(buf);
\r
10466 if (first.sendName) {
\r
10467 sprintf(buf, "name %s\n", gameInfo.white);
\r
10468 SendToProgram(buf, &first);
\r
10470 if (first.sendTime) {
\r
10471 if (first.useColors) {
\r
10472 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10474 SendTimeRemaining(&first, FALSE);
\r
10476 if (first.useColors) {
\r
10477 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10479 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10480 SetMachineThinkingEnables();
\r
10481 first.maybeThinking = TRUE;
\r
10484 if (appData.autoFlipView && flipView) {
\r
10485 flipView = !flipView;
\r
10486 DrawPosition(FALSE, NULL);
\r
10487 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10489 if(bookHit) { // [HGM] book: simulate book reply
\r
10490 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10492 programStats.nodes = programStats.depth = programStats.time =
\r
10493 programStats.score = programStats.got_only_move = 0;
\r
10494 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10496 strcpy(bookMove, "move ");
\r
10497 strcat(bookMove, bookHit);
\r
10498 HandleMachineMove(bookMove, &first);
\r
10504 DisplayTwoMachinesTitle()
\r
10506 char buf[MSG_SIZ];
\r
10507 if (appData.matchGames > 0) {
\r
10508 if (first.twoMachinesColor[0] == 'w') {
\r
10509 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10510 gameInfo.white, gameInfo.black,
\r
10511 first.matchWins, second.matchWins,
\r
10512 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10514 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10515 gameInfo.white, gameInfo.black,
\r
10516 second.matchWins, first.matchWins,
\r
10517 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10520 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10522 DisplayTitle(buf);
\r
10526 TwoMachinesEvent P((void))
\r
10529 char buf[MSG_SIZ];
\r
10530 ChessProgramState *onmove;
\r
10531 char *bookHit = NULL;
\r
10533 if (appData.noChessProgram) return;
\r
10535 switch (gameMode) {
\r
10536 case TwoMachinesPlay:
\r
10538 case MachinePlaysWhite:
\r
10539 case MachinePlaysBlack:
\r
10540 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10541 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10544 /* fall through */
\r
10545 case BeginningOfGame:
\r
10546 case PlayFromGameFile:
\r
10549 if (gameMode != EditGame) return;
\r
10551 case EditPosition:
\r
10552 EditPositionDone();
\r
10554 case AnalyzeMode:
\r
10555 case AnalyzeFile:
\r
10556 ExitAnalyzeMode();
\r
10563 forwardMostMove = currentMove;
\r
10564 ResurrectChessProgram(); /* in case first program isn't running */
\r
10566 if (second.pr == NULL) {
\r
10567 StartChessProgram(&second);
\r
10568 if (second.protocolVersion == 1) {
\r
10569 TwoMachinesEventIfReady();
\r
10571 /* kludge: allow timeout for initial "feature" command */
\r
10573 DisplayMessage("", _("Starting second chess program"));
\r
10574 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10578 DisplayMessage("", "");
\r
10579 InitChessProgram(&second, FALSE);
\r
10580 SendToProgram("force\n", &second);
\r
10581 if (startedFromSetupPosition) {
\r
10582 SendBoard(&second, backwardMostMove);
\r
10583 if (appData.debugMode) {
\r
10584 fprintf(debugFP, "Two Machines\n");
\r
10587 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10588 SendMoveToProgram(i, &second);
\r
10591 gameMode = TwoMachinesPlay;
\r
10595 DisplayTwoMachinesTitle();
\r
10596 firstMove = TRUE;
\r
10597 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10600 onmove = &second;
\r
10603 SendToProgram(first.computerString, &first);
\r
10604 if (first.sendName) {
\r
10605 sprintf(buf, "name %s\n", second.tidy);
\r
10606 SendToProgram(buf, &first);
\r
10608 SendToProgram(second.computerString, &second);
\r
10609 if (second.sendName) {
\r
10610 sprintf(buf, "name %s\n", first.tidy);
\r
10611 SendToProgram(buf, &second);
\r
10615 if (!first.sendTime || !second.sendTime) {
\r
10616 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10617 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10619 if (onmove->sendTime) {
\r
10620 if (onmove->useColors) {
\r
10621 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10623 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10625 if (onmove->useColors) {
\r
10626 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10628 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10629 // SendToProgram("go\n", onmove);
\r
10630 onmove->maybeThinking = TRUE;
\r
10631 SetMachineThinkingEnables();
\r
10635 if(bookHit) { // [HGM] book: simulate book reply
\r
10636 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10638 programStats.nodes = programStats.depth = programStats.time =
\r
10639 programStats.score = programStats.got_only_move = 0;
\r
10640 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10642 strcpy(bookMove, "move ");
\r
10643 strcat(bookMove, bookHit);
\r
10644 HandleMachineMove(bookMove, &first);
\r
10651 if (gameMode == Training) {
\r
10652 SetTrainingModeOff();
\r
10653 gameMode = PlayFromGameFile;
\r
10654 DisplayMessage("", _("Training mode off"));
\r
10656 gameMode = Training;
\r
10657 animateTraining = appData.animate;
\r
10659 /* make sure we are not already at the end of the game */
\r
10660 if (currentMove < forwardMostMove) {
\r
10661 SetTrainingModeOn();
\r
10662 DisplayMessage("", _("Training mode on"));
\r
10664 gameMode = PlayFromGameFile;
\r
10665 DisplayError(_("Already at end of game"), 0);
\r
10674 if (!appData.icsActive) return;
\r
10675 switch (gameMode) {
\r
10676 case IcsPlayingWhite:
\r
10677 case IcsPlayingBlack:
\r
10678 case IcsObserving:
\r
10680 case BeginningOfGame:
\r
10681 case IcsExamining:
\r
10687 case EditPosition:
\r
10688 EditPositionDone();
\r
10691 case AnalyzeMode:
\r
10692 case AnalyzeFile:
\r
10693 ExitAnalyzeMode();
\r
10701 gameMode = IcsIdle;
\r
10712 switch (gameMode) {
\r
10714 SetTrainingModeOff();
\r
10716 case MachinePlaysWhite:
\r
10717 case MachinePlaysBlack:
\r
10718 case BeginningOfGame:
\r
10719 SendToProgram("force\n", &first);
\r
10720 SetUserThinkingEnables();
\r
10722 case PlayFromGameFile:
\r
10723 (void) StopLoadGameTimer();
\r
10724 if (gameFileFP != NULL) {
\r
10725 gameFileFP = NULL;
\r
10728 case EditPosition:
\r
10729 EditPositionDone();
\r
10731 case AnalyzeMode:
\r
10732 case AnalyzeFile:
\r
10733 ExitAnalyzeMode();
\r
10734 SendToProgram("force\n", &first);
\r
10736 case TwoMachinesPlay:
\r
10737 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10738 ResurrectChessProgram();
\r
10739 SetUserThinkingEnables();
\r
10742 ResurrectChessProgram();
\r
10744 case IcsPlayingBlack:
\r
10745 case IcsPlayingWhite:
\r
10746 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10748 case IcsObserving:
\r
10749 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10751 case IcsExamining:
\r
10752 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10763 first.offeredDraw = second.offeredDraw = 0;
\r
10765 if (gameMode == PlayFromGameFile) {
\r
10766 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10767 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10768 DisplayTitle("");
\r
10771 if (gameMode == MachinePlaysWhite ||
\r
10772 gameMode == MachinePlaysBlack ||
\r
10773 gameMode == TwoMachinesPlay ||
\r
10774 gameMode == EndOfGame) {
\r
10775 i = forwardMostMove;
\r
10776 while (i > currentMove) {
\r
10777 SendToProgram("undo\n", &first);
\r
10780 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10781 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10782 DisplayBothClocks();
\r
10783 if (whiteFlag || blackFlag) {
\r
10784 whiteFlag = blackFlag = 0;
\r
10786 DisplayTitle("");
\r
10789 gameMode = EditGame;
\r
10796 EditPositionEvent()
\r
10798 if (gameMode == EditPosition) {
\r
10804 if (gameMode != EditGame) return;
\r
10806 gameMode = EditPosition;
\r
10809 if (currentMove > 0)
\r
10810 CopyBoard(boards[0], boards[currentMove]);
\r
10812 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10814 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10815 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10820 ExitAnalyzeMode()
\r
10822 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10823 if (appData.icsEngineAnalyze) {
\r
10824 appData.icsEngineAnalyze = FALSE;
\r
10826 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10828 if (first.analysisSupport && first.analyzing) {
\r
10829 SendToProgram("exit\n", &first);
\r
10830 first.analyzing = FALSE;
\r
10832 AnalysisPopDown();
\r
10833 thinkOutput[0] = NULLCHAR;
\r
10837 EditPositionDone()
\r
10839 startedFromSetupPosition = TRUE;
\r
10840 InitChessProgram(&first, FALSE);
\r
10841 SendToProgram("force\n", &first);
\r
10842 if (blackPlaysFirst) {
\r
10843 strcpy(moveList[0], "");
\r
10844 strcpy(parseList[0], "");
\r
10845 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10846 CopyBoard(boards[1], boards[0]);
\r
10847 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10849 epStatus[1] = epStatus[0];
\r
10850 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10853 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10855 SendBoard(&first, forwardMostMove);
\r
10856 if (appData.debugMode) {
\r
10857 fprintf(debugFP, "EditPosDone\n");
\r
10859 DisplayTitle("");
\r
10860 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10861 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10862 gameMode = EditGame;
\r
10864 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10865 ClearHighlights(); /* [AS] */
\r
10868 /* Pause for `ms' milliseconds */
\r
10869 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10876 GetTimeMark(&m1);
\r
10878 GetTimeMark(&m2);
\r
10879 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10882 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10884 SendMultiLineToICS(buf)
\r
10887 char temp[MSG_SIZ+1], *p;
\r
10890 len = strlen(buf);
\r
10891 if (len > MSG_SIZ)
\r
10894 strncpy(temp, buf, len);
\r
10899 if (*p == '\n' || *p == '\r')
\r
10904 strcat(temp, "\n");
\r
10906 SendToPlayer(temp, strlen(temp));
\r
10910 SetWhiteToPlayEvent()
\r
10912 if (gameMode == EditPosition) {
\r
10913 blackPlaysFirst = FALSE;
\r
10914 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10915 } else if (gameMode == IcsExamining) {
\r
10916 SendToICS(ics_prefix);
\r
10917 SendToICS("tomove white\n");
\r
10922 SetBlackToPlayEvent()
\r
10924 if (gameMode == EditPosition) {
\r
10925 blackPlaysFirst = TRUE;
\r
10926 currentMove = 1; /* kludge */
\r
10927 DisplayBothClocks();
\r
10929 } else if (gameMode == IcsExamining) {
\r
10930 SendToICS(ics_prefix);
\r
10931 SendToICS("tomove black\n");
\r
10936 EditPositionMenuEvent(selection, x, y)
\r
10937 ChessSquare selection;
\r
10940 char buf[MSG_SIZ];
\r
10941 ChessSquare piece = boards[0][y][x];
\r
10943 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10945 switch (selection) {
\r
10947 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10948 SendToICS(ics_prefix);
\r
10949 SendToICS("bsetup clear\n");
\r
10950 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10951 SendToICS(ics_prefix);
\r
10952 SendToICS("clearboard\n");
\r
10954 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10955 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10956 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10957 if (gameMode == IcsExamining) {
\r
10958 if (boards[currentMove][y][x] != EmptySquare) {
\r
10959 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10960 AAA + x, ONE + y);
\r
10964 boards[0][y][x] = p;
\r
10969 if (gameMode == EditPosition) {
\r
10970 DrawPosition(FALSE, boards[0]);
\r
10975 SetWhiteToPlayEvent();
\r
10979 SetBlackToPlayEvent();
\r
10982 case EmptySquare:
\r
10983 if (gameMode == IcsExamining) {
\r
10984 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10987 boards[0][y][x] = EmptySquare;
\r
10988 DrawPosition(FALSE, boards[0]);
\r
10992 case PromotePiece:
\r
10993 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
10994 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
10995 selection = (ChessSquare) (PROMOTED piece);
\r
10996 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
10997 else selection = (ChessSquare)((int)piece - 1);
\r
10998 goto defaultlabel;
\r
11000 case DemotePiece:
\r
11001 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11002 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11003 selection = (ChessSquare) (DEMOTED piece);
\r
11004 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11005 else selection = (ChessSquare)((int)piece + 1);
\r
11006 goto defaultlabel;
\r
11010 if(gameInfo.variant == VariantShatranj ||
\r
11011 gameInfo.variant == VariantXiangqi ||
\r
11012 gameInfo.variant == VariantCourier )
\r
11013 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11014 goto defaultlabel;
\r
11018 if(gameInfo.variant == VariantXiangqi)
\r
11019 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11020 if(gameInfo.variant == VariantKnightmate)
\r
11021 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11024 if (gameMode == IcsExamining) {
\r
11025 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11026 PieceToChar(selection), AAA + x, ONE + y);
\r
11029 boards[0][y][x] = selection;
\r
11030 DrawPosition(FALSE, boards[0]);
\r
11038 DropMenuEvent(selection, x, y)
\r
11039 ChessSquare selection;
\r
11042 ChessMove moveType;
\r
11044 switch (gameMode) {
\r
11045 case IcsPlayingWhite:
\r
11046 case MachinePlaysBlack:
\r
11047 if (!WhiteOnMove(currentMove)) {
\r
11048 DisplayMoveError(_("It is Black's turn"));
\r
11051 moveType = WhiteDrop;
\r
11053 case IcsPlayingBlack:
\r
11054 case MachinePlaysWhite:
\r
11055 if (WhiteOnMove(currentMove)) {
\r
11056 DisplayMoveError(_("It is White's turn"));
\r
11059 moveType = BlackDrop;
\r
11062 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11068 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11069 selection = (ChessSquare) ((int) selection
\r
11070 + (int) BlackPawn - (int) WhitePawn);
\r
11072 if (boards[currentMove][y][x] != EmptySquare) {
\r
11073 DisplayMoveError(_("That square is occupied"));
\r
11077 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11083 /* Accept a pending offer of any kind from opponent */
\r
11085 if (appData.icsActive) {
\r
11086 SendToICS(ics_prefix);
\r
11087 SendToICS("accept\n");
\r
11088 } else if (cmailMsgLoaded) {
\r
11089 if (currentMove == cmailOldMove &&
\r
11090 commentList[cmailOldMove] != NULL &&
\r
11091 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11092 "Black offers a draw" : "White offers a draw")) {
\r
11094 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11095 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11097 DisplayError(_("There is no pending offer on this move"), 0);
\r
11098 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11101 /* Not used for offers from chess program */
\r
11108 /* Decline a pending offer of any kind from opponent */
\r
11110 if (appData.icsActive) {
\r
11111 SendToICS(ics_prefix);
\r
11112 SendToICS("decline\n");
\r
11113 } else if (cmailMsgLoaded) {
\r
11114 if (currentMove == cmailOldMove &&
\r
11115 commentList[cmailOldMove] != NULL &&
\r
11116 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11117 "Black offers a draw" : "White offers a draw")) {
\r
11119 AppendComment(cmailOldMove, "Draw declined");
\r
11120 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11121 #endif /*NOTDEF*/
\r
11123 DisplayError(_("There is no pending offer on this move"), 0);
\r
11126 /* Not used for offers from chess program */
\r
11133 /* Issue ICS rematch command */
\r
11134 if (appData.icsActive) {
\r
11135 SendToICS(ics_prefix);
\r
11136 SendToICS("rematch\n");
\r
11143 /* Call your opponent's flag (claim a win on time) */
\r
11144 if (appData.icsActive) {
\r
11145 SendToICS(ics_prefix);
\r
11146 SendToICS("flag\n");
\r
11148 switch (gameMode) {
\r
11151 case MachinePlaysWhite:
\r
11154 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11157 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11159 DisplayError(_("Your opponent is not out of time"), 0);
\r
11162 case MachinePlaysBlack:
\r
11165 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11168 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11170 DisplayError(_("Your opponent is not out of time"), 0);
\r
11180 /* Offer draw or accept pending draw offer from opponent */
\r
11182 if (appData.icsActive) {
\r
11183 /* Note: tournament rules require draw offers to be
\r
11184 made after you make your move but before you punch
\r
11185 your clock. Currently ICS doesn't let you do that;
\r
11186 instead, you immediately punch your clock after making
\r
11187 a move, but you can offer a draw at any time. */
\r
11189 SendToICS(ics_prefix);
\r
11190 SendToICS("draw\n");
\r
11191 } else if (cmailMsgLoaded) {
\r
11192 if (currentMove == cmailOldMove &&
\r
11193 commentList[cmailOldMove] != NULL &&
\r
11194 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11195 "Black offers a draw" : "White offers a draw")) {
\r
11196 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11197 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11198 } else if (currentMove == cmailOldMove + 1) {
\r
11199 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11200 "White offers a draw" : "Black offers a draw";
\r
11201 AppendComment(currentMove, offer);
\r
11202 DisplayComment(currentMove - 1, offer);
\r
11203 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11205 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11206 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11208 } else if (first.offeredDraw) {
\r
11209 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11211 if (first.sendDrawOffers) {
\r
11212 SendToProgram("draw\n", &first);
\r
11213 userOfferedDraw = TRUE;
\r
11221 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11223 if (appData.icsActive) {
\r
11224 SendToICS(ics_prefix);
\r
11225 SendToICS("adjourn\n");
\r
11227 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11235 /* Offer Abort or accept pending Abort offer from opponent */
\r
11237 if (appData.icsActive) {
\r
11238 SendToICS(ics_prefix);
\r
11239 SendToICS("abort\n");
\r
11241 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11248 /* Resign. You can do this even if it's not your turn. */
\r
11250 if (appData.icsActive) {
\r
11251 SendToICS(ics_prefix);
\r
11252 SendToICS("resign\n");
\r
11254 switch (gameMode) {
\r
11255 case MachinePlaysWhite:
\r
11256 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11258 case MachinePlaysBlack:
\r
11259 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11262 if (cmailMsgLoaded) {
\r
11264 if (WhiteOnMove(cmailOldMove)) {
\r
11265 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11267 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11269 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11280 StopObservingEvent()
\r
11282 /* Stop observing current games */
\r
11283 SendToICS(ics_prefix);
\r
11284 SendToICS("unobserve\n");
\r
11288 StopExaminingEvent()
\r
11290 /* Stop observing current game */
\r
11291 SendToICS(ics_prefix);
\r
11292 SendToICS("unexamine\n");
\r
11296 ForwardInner(target)
\r
11301 if (appData.debugMode)
\r
11302 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11303 target, currentMove, forwardMostMove);
\r
11305 if (gameMode == EditPosition)
\r
11308 if (gameMode == PlayFromGameFile && !pausing)
\r
11311 if (gameMode == IcsExamining && pausing)
\r
11312 limit = pauseExamForwardMostMove;
\r
11314 limit = forwardMostMove;
\r
11316 if (target > limit) target = limit;
\r
11318 if (target > 0 && moveList[target - 1][0]) {
\r
11319 int fromX, fromY, toX, toY;
\r
11320 toX = moveList[target - 1][2] - AAA;
\r
11321 toY = moveList[target - 1][3] - ONE;
\r
11322 if (moveList[target - 1][1] == '@') {
\r
11323 if (appData.highlightLastMove) {
\r
11324 SetHighlights(-1, -1, toX, toY);
\r
11327 fromX = moveList[target - 1][0] - AAA;
\r
11328 fromY = moveList[target - 1][1] - ONE;
\r
11329 if (target == currentMove + 1) {
\r
11330 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11332 if (appData.highlightLastMove) {
\r
11333 SetHighlights(fromX, fromY, toX, toY);
\r
11337 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11338 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11339 gameMode == AnalyzeFile) {
\r
11340 while (currentMove < target) {
\r
11341 SendMoveToProgram(currentMove++, &first);
\r
11344 currentMove = target;
\r
11347 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11348 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11349 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11351 DisplayBothClocks();
\r
11352 DisplayMove(currentMove - 1);
\r
11353 DrawPosition(FALSE, boards[currentMove]);
\r
11354 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11355 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11356 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11364 if (gameMode == IcsExamining && !pausing) {
\r
11365 SendToICS(ics_prefix);
\r
11366 SendToICS("forward\n");
\r
11368 ForwardInner(currentMove + 1);
\r
11375 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11376 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11377 * the remaining moves to the engine. Otherwise we get analysis output
\r
11378 * after each move.
\r
11380 if (first.analysisSupport) {
\r
11381 SendToProgram("exit\nforce\n", &first);
\r
11382 first.analyzing = FALSE;
\r
11386 if (gameMode == IcsExamining && !pausing) {
\r
11387 SendToICS(ics_prefix);
\r
11388 SendToICS("forward 999999\n");
\r
11390 ForwardInner(forwardMostMove);
\r
11393 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11394 /* we have fed all the moves, so reactivate analysis mode */
\r
11395 SendToProgram("analyze\n", &first);
\r
11396 first.analyzing = TRUE;
\r
11397 /*first.maybeThinking = TRUE;*/
\r
11398 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11403 BackwardInner(target)
\r
11406 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11408 if (appData.debugMode)
\r
11409 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11410 target, currentMove, forwardMostMove);
\r
11412 if (gameMode == EditPosition) return;
\r
11413 if (currentMove <= backwardMostMove) {
\r
11414 ClearHighlights();
\r
11415 DrawPosition(full_redraw, boards[currentMove]);
\r
11418 if (gameMode == PlayFromGameFile && !pausing)
\r
11421 if (moveList[target][0]) {
\r
11422 int fromX, fromY, toX, toY;
\r
11423 toX = moveList[target][2] - AAA;
\r
11424 toY = moveList[target][3] - ONE;
\r
11425 if (moveList[target][1] == '@') {
\r
11426 if (appData.highlightLastMove) {
\r
11427 SetHighlights(-1, -1, toX, toY);
\r
11430 fromX = moveList[target][0] - AAA;
\r
11431 fromY = moveList[target][1] - ONE;
\r
11432 if (target == currentMove - 1) {
\r
11433 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11435 if (appData.highlightLastMove) {
\r
11436 SetHighlights(fromX, fromY, toX, toY);
\r
11440 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11441 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11442 while (currentMove > target) {
\r
11443 SendToProgram("undo\n", &first);
\r
11447 currentMove = target;
\r
11450 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11451 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11452 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11454 DisplayBothClocks();
\r
11455 DisplayMove(currentMove - 1);
\r
11456 DrawPosition(full_redraw, boards[currentMove]);
\r
11457 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11458 // [HGM] PV info: routine tests if comment empty
\r
11459 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11465 if (gameMode == IcsExamining && !pausing) {
\r
11466 SendToICS(ics_prefix);
\r
11467 SendToICS("backward\n");
\r
11469 BackwardInner(currentMove - 1);
\r
11476 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11477 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11478 * all the moves. Otherwise we get analysis output after each undo.
\r
11480 if (first.analysisSupport) {
\r
11481 SendToProgram("exit\nforce\n", &first);
\r
11482 first.analyzing = FALSE;
\r
11486 if (gameMode == IcsExamining && !pausing) {
\r
11487 SendToICS(ics_prefix);
\r
11488 SendToICS("backward 999999\n");
\r
11490 BackwardInner(backwardMostMove);
\r
11493 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11494 /* we have fed all the moves, so reactivate analysis mode */
\r
11495 SendToProgram("analyze\n", &first);
\r
11496 first.analyzing = TRUE;
\r
11497 /*first.maybeThinking = TRUE;*/
\r
11498 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11503 ToNrEvent(int to)
\r
11505 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11506 if (to >= forwardMostMove) to = forwardMostMove;
\r
11507 if (to <= backwardMostMove) to = backwardMostMove;
\r
11508 if (to < currentMove) {
\r
11509 BackwardInner(to);
\r
11511 ForwardInner(to);
\r
11518 if (gameMode != IcsExamining) {
\r
11519 DisplayError(_("You are not examining a game"), 0);
\r
11523 DisplayError(_("You can't revert while pausing"), 0);
\r
11526 SendToICS(ics_prefix);
\r
11527 SendToICS("revert\n");
\r
11531 RetractMoveEvent()
\r
11533 switch (gameMode) {
\r
11534 case MachinePlaysWhite:
\r
11535 case MachinePlaysBlack:
\r
11536 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11537 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11540 if (forwardMostMove < 2) return;
\r
11541 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11542 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11543 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11544 DisplayBothClocks();
\r
11545 DisplayMove(currentMove - 1);
\r
11546 ClearHighlights();/*!! could figure this out*/
\r
11547 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11548 SendToProgram("remove\n", &first);
\r
11549 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11552 case BeginningOfGame:
\r
11556 case IcsPlayingWhite:
\r
11557 case IcsPlayingBlack:
\r
11558 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11559 SendToICS(ics_prefix);
\r
11560 SendToICS("takeback 2\n");
\r
11562 SendToICS(ics_prefix);
\r
11563 SendToICS("takeback 1\n");
\r
11572 ChessProgramState *cps;
\r
11574 switch (gameMode) {
\r
11575 case MachinePlaysWhite:
\r
11576 if (!WhiteOnMove(forwardMostMove)) {
\r
11577 DisplayError(_("It is your turn"), 0);
\r
11582 case MachinePlaysBlack:
\r
11583 if (WhiteOnMove(forwardMostMove)) {
\r
11584 DisplayError(_("It is your turn"), 0);
\r
11589 case TwoMachinesPlay:
\r
11590 if (WhiteOnMove(forwardMostMove) ==
\r
11591 (first.twoMachinesColor[0] == 'w')) {
\r
11597 case BeginningOfGame:
\r
11601 SendToProgram("?\n", cps);
\r
11605 TruncateGameEvent()
\r
11608 if (gameMode != EditGame) return;
\r
11615 if (forwardMostMove > currentMove) {
\r
11616 if (gameInfo.resultDetails != NULL) {
\r
11617 free(gameInfo.resultDetails);
\r
11618 gameInfo.resultDetails = NULL;
\r
11619 gameInfo.result = GameUnfinished;
\r
11621 forwardMostMove = currentMove;
\r
11622 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11630 if (appData.noChessProgram) return;
\r
11631 switch (gameMode) {
\r
11632 case MachinePlaysWhite:
\r
11633 if (WhiteOnMove(forwardMostMove)) {
\r
11634 DisplayError(_("Wait until your turn"), 0);
\r
11638 case BeginningOfGame:
\r
11639 case MachinePlaysBlack:
\r
11640 if (!WhiteOnMove(forwardMostMove)) {
\r
11641 DisplayError(_("Wait until your turn"), 0);
\r
11646 DisplayError(_("No hint available"), 0);
\r
11649 SendToProgram("hint\n", &first);
\r
11650 hintRequested = TRUE;
\r
11656 if (appData.noChessProgram) return;
\r
11657 switch (gameMode) {
\r
11658 case MachinePlaysWhite:
\r
11659 if (WhiteOnMove(forwardMostMove)) {
\r
11660 DisplayError(_("Wait until your turn"), 0);
\r
11664 case BeginningOfGame:
\r
11665 case MachinePlaysBlack:
\r
11666 if (!WhiteOnMove(forwardMostMove)) {
\r
11667 DisplayError(_("Wait until your turn"), 0);
\r
11671 case EditPosition:
\r
11672 EditPositionDone();
\r
11674 case TwoMachinesPlay:
\r
11679 SendToProgram("bk\n", &first);
\r
11680 bookOutput[0] = NULLCHAR;
\r
11681 bookRequested = TRUE;
\r
11687 char *tags = PGNTags(&gameInfo);
\r
11688 TagsPopUp(tags, CmailMsg());
\r
11692 /* end button procedures */
\r
11695 PrintPosition(fp, move)
\r
11701 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11702 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11703 char c = PieceToChar(boards[move][i][j]);
\r
11704 fputc(c == 'x' ? '.' : c, fp);
\r
11705 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11708 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11709 fprintf(fp, "white to play\n");
\r
11711 fprintf(fp, "black to play\n");
\r
11715 PrintOpponents(fp)
\r
11718 if (gameInfo.white != NULL) {
\r
11719 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11721 fprintf(fp, "\n");
\r
11725 /* Find last component of program's own name, using some heuristics */
\r
11727 TidyProgramName(prog, host, buf)
\r
11728 char *prog, *host, buf[MSG_SIZ];
\r
11731 int local = (strcmp(host, "localhost") == 0);
\r
11732 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11734 while (*p == ' ') p++;
\r
11737 if (*prog == '"' || *prog == '\'') {
\r
11738 q = strchr(prog + 1, *prog);
\r
11740 q = strchr(prog, ' ');
\r
11742 if (q == NULL) q = prog + strlen(prog);
\r
11744 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11746 if(p == prog && *p == '"') p++;
\r
11747 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11748 memcpy(buf, p, q - p);
\r
11749 buf[q - p] = NULLCHAR;
\r
11751 strcat(buf, "@");
\r
11752 strcat(buf, host);
\r
11757 TimeControlTagValue()
\r
11759 char buf[MSG_SIZ];
\r
11760 if (!appData.clockMode) {
\r
11761 strcpy(buf, "-");
\r
11762 } else if (movesPerSession > 0) {
\r
11763 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11764 } else if (timeIncrement == 0) {
\r
11765 sprintf(buf, "%ld", timeControl/1000);
\r
11767 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11769 return StrSave(buf);
\r
11775 /* This routine is used only for certain modes */
\r
11776 VariantClass v = gameInfo.variant;
\r
11777 ClearGameInfo(&gameInfo);
\r
11778 gameInfo.variant = v;
\r
11780 switch (gameMode) {
\r
11781 case MachinePlaysWhite:
\r
11782 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11783 gameInfo.site = StrSave(HostName());
\r
11784 gameInfo.date = PGNDate();
\r
11785 gameInfo.round = StrSave("-");
\r
11786 gameInfo.white = StrSave(first.tidy);
\r
11787 gameInfo.black = StrSave(UserName());
\r
11788 gameInfo.timeControl = TimeControlTagValue();
\r
11791 case MachinePlaysBlack:
\r
11792 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11793 gameInfo.site = StrSave(HostName());
\r
11794 gameInfo.date = PGNDate();
\r
11795 gameInfo.round = StrSave("-");
\r
11796 gameInfo.white = StrSave(UserName());
\r
11797 gameInfo.black = StrSave(first.tidy);
\r
11798 gameInfo.timeControl = TimeControlTagValue();
\r
11801 case TwoMachinesPlay:
\r
11802 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11803 gameInfo.site = StrSave(HostName());
\r
11804 gameInfo.date = PGNDate();
\r
11805 if (matchGame > 0) {
\r
11806 char buf[MSG_SIZ];
\r
11807 sprintf(buf, "%d", matchGame);
\r
11808 gameInfo.round = StrSave(buf);
\r
11810 gameInfo.round = StrSave("-");
\r
11812 if (first.twoMachinesColor[0] == 'w') {
\r
11813 gameInfo.white = StrSave(first.tidy);
\r
11814 gameInfo.black = StrSave(second.tidy);
\r
11816 gameInfo.white = StrSave(second.tidy);
\r
11817 gameInfo.black = StrSave(first.tidy);
\r
11819 gameInfo.timeControl = TimeControlTagValue();
\r
11823 gameInfo.event = StrSave("Edited game");
\r
11824 gameInfo.site = StrSave(HostName());
\r
11825 gameInfo.date = PGNDate();
\r
11826 gameInfo.round = StrSave("-");
\r
11827 gameInfo.white = StrSave("-");
\r
11828 gameInfo.black = StrSave("-");
\r
11831 case EditPosition:
\r
11832 gameInfo.event = StrSave("Edited position");
\r
11833 gameInfo.site = StrSave(HostName());
\r
11834 gameInfo.date = PGNDate();
\r
11835 gameInfo.round = StrSave("-");
\r
11836 gameInfo.white = StrSave("-");
\r
11837 gameInfo.black = StrSave("-");
\r
11840 case IcsPlayingWhite:
\r
11841 case IcsPlayingBlack:
\r
11842 case IcsObserving:
\r
11843 case IcsExamining:
\r
11846 case PlayFromGameFile:
\r
11847 gameInfo.event = StrSave("Game from non-PGN file");
\r
11848 gameInfo.site = StrSave(HostName());
\r
11849 gameInfo.date = PGNDate();
\r
11850 gameInfo.round = StrSave("-");
\r
11851 gameInfo.white = StrSave("?");
\r
11852 gameInfo.black = StrSave("?");
\r
11861 ReplaceComment(index, text)
\r
11867 while (*text == '\n') text++;
\r
11868 len = strlen(text);
\r
11869 while (len > 0 && text[len - 1] == '\n') len--;
\r
11871 if (commentList[index] != NULL)
\r
11872 free(commentList[index]);
\r
11875 commentList[index] = NULL;
\r
11878 commentList[index] = (char *) malloc(len + 2);
\r
11879 strncpy(commentList[index], text, len);
\r
11880 commentList[index][len] = '\n';
\r
11881 commentList[index][len + 1] = NULLCHAR;
\r
11894 if (ch == '\r') continue;
\r
11896 } while (ch != '\0');
\r
11900 AppendComment(index, text)
\r
11907 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11910 while (*text == '\n') text++;
\r
11911 len = strlen(text);
\r
11912 while (len > 0 && text[len - 1] == '\n') len--;
\r
11914 if (len == 0) return;
\r
11916 if (commentList[index] != NULL) {
\r
11917 old = commentList[index];
\r
11918 oldlen = strlen(old);
\r
11919 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11920 strcpy(commentList[index], old);
\r
11922 strncpy(&commentList[index][oldlen], text, len);
\r
11923 commentList[index][oldlen + len] = '\n';
\r
11924 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11926 commentList[index] = (char *) malloc(len + 2);
\r
11927 strncpy(commentList[index], text, len);
\r
11928 commentList[index][len] = '\n';
\r
11929 commentList[index][len + 1] = NULLCHAR;
\r
11933 static char * FindStr( char * text, char * sub_text )
\r
11935 char * result = strstr( text, sub_text );
\r
11937 if( result != NULL ) {
\r
11938 result += strlen( sub_text );
\r
11944 /* [AS] Try to extract PV info from PGN comment */
\r
11945 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11946 char *GetInfoFromComment( int index, char * text )
\r
11948 char * sep = text;
\r
11950 if( text != NULL && index > 0 ) {
\r
11953 int time = -1, sec = 0, deci;
\r
11954 char * s_eval = FindStr( text, "[%eval " );
\r
11955 char * s_emt = FindStr( text, "[%emt " );
\r
11957 if( s_eval != NULL || s_emt != NULL ) {
\r
11961 if( s_eval != NULL ) {
\r
11962 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11966 if( delim != ']' ) {
\r
11971 if( s_emt != NULL ) {
\r
11975 /* We expect something like: [+|-]nnn.nn/dd */
\r
11976 int score_lo = 0;
\r
11978 sep = strchr( text, '/' );
\r
11979 if( sep == NULL || sep < (text+4) ) {
\r
11983 time = -1; sec = -1; deci = -1;
\r
11984 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11985 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
11986 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11987 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
11991 if( score_lo < 0 || score_lo >= 100 ) {
\r
11995 if(sec >= 0) time = 600*time + 10*sec; else
\r
11996 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
11998 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12000 /* [HGM] PV time: now locate end of PV info */
\r
12001 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12003 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12005 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12007 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12008 while(*sep == ' ') sep++;
\r
12011 if( depth <= 0 ) {
\r
12019 pvInfoList[index-1].depth = depth;
\r
12020 pvInfoList[index-1].score = score;
\r
12021 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12027 SendToProgram(message, cps)
\r
12029 ChessProgramState *cps;
\r
12031 int count, outCount, error;
\r
12032 char buf[MSG_SIZ];
\r
12034 if (cps->pr == NULL) return;
\r
12037 if (appData.debugMode) {
\r
12039 GetTimeMark(&now);
\r
12040 fprintf(debugFP, "%ld >%-6s: %s",
\r
12041 SubtractTimeMarks(&now, &programStartTime),
\r
12042 cps->which, message);
\r
12045 count = strlen(message);
\r
12046 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12047 if (outCount < count && !exiting
\r
12048 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12049 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12050 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12051 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12052 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12053 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12055 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12057 gameInfo.resultDetails = buf;
\r
12059 DisplayFatalError(buf, error, 1);
\r
12064 ReceiveFromProgram(isr, closure, message, count, error)
\r
12065 InputSourceRef isr;
\r
12066 VOIDSTAR closure;
\r
12072 char buf[MSG_SIZ];
\r
12073 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12075 if (isr != cps->isr) return; /* Killed intentionally */
\r
12076 if (count <= 0) {
\r
12077 if (count == 0) {
\r
12079 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12080 cps->which, cps->program);
\r
12081 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12082 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12083 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12084 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12086 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12088 gameInfo.resultDetails = buf;
\r
12090 RemoveInputSource(cps->isr);
\r
12091 DisplayFatalError(buf, 0, 1);
\r
12094 _("Error reading from %s chess program (%s)"),
\r
12095 cps->which, cps->program);
\r
12096 RemoveInputSource(cps->isr);
\r
12098 /* [AS] Program is misbehaving badly... kill it */
\r
12099 if( count == -2 ) {
\r
12100 DestroyChildProcess( cps->pr, 9 );
\r
12101 cps->pr = NoProc;
\r
12104 DisplayFatalError(buf, error, 1);
\r
12109 if ((end_str = strchr(message, '\r')) != NULL)
\r
12110 *end_str = NULLCHAR;
\r
12111 if ((end_str = strchr(message, '\n')) != NULL)
\r
12112 *end_str = NULLCHAR;
\r
12114 if (appData.debugMode) {
\r
12115 TimeMark now; int print = 1;
\r
12116 char *quote = ""; char c; int i;
\r
12118 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12119 char start = message[0];
\r
12120 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12121 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12122 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12123 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12124 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12125 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12126 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12127 { quote = "# "; print = (appData.engineComments == 2); }
\r
12128 message[0] = start; // restore original message
\r
12131 GetTimeMark(&now);
\r
12132 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12133 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12139 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12140 if (appData.icsEngineAnalyze) {
\r
12141 if (strstr(message, "whisper") != NULL ||
\r
12142 strstr(message, "kibitz") != NULL ||
\r
12143 strstr(message, "tellics") != NULL) return;
\r
12146 HandleMachineMove(message, cps);
\r
12151 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12152 ChessProgramState *cps;
\r
12153 int mps, inc, sd, st;
\r
12156 char buf[MSG_SIZ];
\r
12159 if( timeControl_2 > 0 ) {
\r
12160 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12161 tc = timeControl_2;
\r
12164 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12165 inc /= cps->timeOdds;
\r
12166 st /= cps->timeOdds;
\r
12168 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12171 /* Set exact time per move, normally using st command */
\r
12172 if (cps->stKludge) {
\r
12173 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12174 seconds = st % 60;
\r
12175 if (seconds == 0) {
\r
12176 sprintf(buf, "level 1 %d\n", st/60);
\r
12178 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12181 sprintf(buf, "st %d\n", st);
\r
12184 /* Set conventional or incremental time control, using level command */
\r
12185 if (seconds == 0) {
\r
12186 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12187 Fixed in later versions, but still avoid :seconds
\r
12188 when seconds is 0. */
\r
12189 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12191 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12192 seconds, inc/1000);
\r
12195 SendToProgram(buf, cps);
\r
12197 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12198 /* Orthogonally, limit search to given depth */
\r
12200 if (cps->sdKludge) {
\r
12201 sprintf(buf, "depth\n%d\n", sd);
\r
12203 sprintf(buf, "sd %d\n", sd);
\r
12205 SendToProgram(buf, cps);
\r
12208 if(cps->nps > 0) { /* [HGM] nps */
\r
12209 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12211 sprintf(buf, "nps %d\n", cps->nps);
\r
12212 SendToProgram(buf, cps);
\r
12217 ChessProgramState *WhitePlayer()
\r
12218 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12220 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12221 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12227 SendTimeRemaining(cps, machineWhite)
\r
12228 ChessProgramState *cps;
\r
12229 int /*boolean*/ machineWhite;
\r
12231 char message[MSG_SIZ];
\r
12232 long time, otime;
\r
12234 /* Note: this routine must be called when the clocks are stopped
\r
12235 or when they have *just* been set or switched; otherwise
\r
12236 it will be off by the time since the current tick started.
\r
12238 if (machineWhite) {
\r
12239 time = whiteTimeRemaining / 10;
\r
12240 otime = blackTimeRemaining / 10;
\r
12242 time = blackTimeRemaining / 10;
\r
12243 otime = whiteTimeRemaining / 10;
\r
12245 /* [HGM] translate opponent's time by time-odds factor */
\r
12246 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12247 if (appData.debugMode) {
\r
12248 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12251 if (time <= 0) time = 1;
\r
12252 if (otime <= 0) otime = 1;
\r
12254 sprintf(message, "time %ld\n", time);
\r
12255 SendToProgram(message, cps);
\r
12257 sprintf(message, "otim %ld\n", otime);
\r
12258 SendToProgram(message, cps);
\r
12262 BoolFeature(p, name, loc, cps)
\r
12266 ChessProgramState *cps;
\r
12268 char buf[MSG_SIZ];
\r
12269 int len = strlen(name);
\r
12271 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12273 sscanf(*p, "%d", &val);
\r
12274 *loc = (val != 0);
\r
12275 while (**p && **p != ' ') (*p)++;
\r
12276 sprintf(buf, "accepted %s\n", name);
\r
12277 SendToProgram(buf, cps);
\r
12284 IntFeature(p, name, loc, cps)
\r
12288 ChessProgramState *cps;
\r
12290 char buf[MSG_SIZ];
\r
12291 int len = strlen(name);
\r
12292 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12294 sscanf(*p, "%d", loc);
\r
12295 while (**p && **p != ' ') (*p)++;
\r
12296 sprintf(buf, "accepted %s\n", name);
\r
12297 SendToProgram(buf, cps);
\r
12304 StringFeature(p, name, loc, cps)
\r
12308 ChessProgramState *cps;
\r
12310 char buf[MSG_SIZ];
\r
12311 int len = strlen(name);
\r
12312 if (strncmp((*p), name, len) == 0
\r
12313 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12315 sscanf(*p, "%[^\"]", loc);
\r
12316 while (**p && **p != '\"') (*p)++;
\r
12317 if (**p == '\"') (*p)++;
\r
12318 sprintf(buf, "accepted %s\n", name);
\r
12319 SendToProgram(buf, cps);
\r
12326 ParseOption(Option *opt, ChessProgramState *cps)
\r
12327 // [HGM] options: process the string that defines an engine option, and determine
\r
12328 // name, type, default value, and allowed value range
\r
12330 char *p, *q, buf[MSG_SIZ];
\r
12331 int n, min = (-1)<<31, max = 1<<31, def;
\r
12333 if(p = strstr(opt->name, " -spin ")) {
\r
12334 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12335 if(max < min) max = min; // enforce consistency
\r
12336 if(def < min) def = min;
\r
12337 if(def > max) def = max;
\r
12338 opt->value = def;
\r
12341 opt->type = Spin;
\r
12342 } else if(p = strstr(opt->name, " -string ")) {
\r
12343 opt->textValue = p+9;
\r
12344 opt->type = TextBox;
\r
12345 } else if(p = strstr(opt->name, " -check ")) {
\r
12346 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12347 opt->value = (def != 0);
\r
12348 opt->type = CheckBox;
\r
12349 } else if(p = strstr(opt->name, " -combo ")) {
\r
12350 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12351 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12352 opt->value = n = 0;
\r
12353 while(q = StrStr(q, " /// ")) {
\r
12354 n++; *q = 0; // count choices, and null-terminate each of them
\r
12356 if(*q == '*') { // remember default, which is marked with * prefix
\r
12360 cps->comboList[cps->comboCnt++] = q;
\r
12362 cps->comboList[cps->comboCnt++] = NULL;
\r
12363 opt->max = n + 1;
\r
12364 opt->type = ComboBox;
\r
12365 } else if(p = strstr(opt->name, " -button")) {
\r
12366 opt->type = Button;
\r
12367 } else if(p = strstr(opt->name, " -save")) {
\r
12368 opt->type = SaveButton;
\r
12369 } else return FALSE;
\r
12370 *p = 0; // terminate option name
\r
12371 // now look if the command-line options define a setting for this engine option.
\r
12372 if(cps->optionSettings && cps->optionSettings[0])
\r
12373 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12374 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12375 sprintf(buf, "option %s", p);
\r
12376 if(p = strstr(buf, ",")) *p = 0;
\r
12377 strcat(buf, "\n");
\r
12378 SendToProgram(buf, cps);
\r
12384 FeatureDone(cps, val)
\r
12385 ChessProgramState* cps;
\r
12388 DelayedEventCallback cb = GetDelayedEvent();
\r
12389 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12390 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12391 CancelDelayedEvent();
\r
12392 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12394 cps->initDone = val;
\r
12397 /* Parse feature command from engine */
\r
12399 ParseFeatures(args, cps)
\r
12401 ChessProgramState *cps;
\r
12406 char buf[MSG_SIZ];
\r
12409 while (*p == ' ') p++;
\r
12410 if (*p == NULLCHAR) return;
\r
12412 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12413 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12414 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12415 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12416 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12417 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12418 /* Engine can disable reuse, but can't enable it if user said no */
\r
12419 if (!val) cps->reuse = FALSE;
\r
12422 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12423 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12424 if (gameMode == TwoMachinesPlay) {
\r
12425 DisplayTwoMachinesTitle();
\r
12427 DisplayTitle("");
\r
12431 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12432 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12433 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12434 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12435 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12436 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12437 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12438 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12439 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12440 if (IntFeature(&p, "done", &val, cps)) {
\r
12441 FeatureDone(cps, val);
\r
12444 /* Added by Tord: */
\r
12445 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12446 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12447 /* End of additions by Tord */
\r
12449 /* [HGM] added features: */
\r
12450 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12451 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12452 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12453 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12454 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12455 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12456 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12457 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12458 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12459 cps->nrOptions--;
\r
12460 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12461 DisplayError(buf, 0);
\r
12465 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12466 /* End of additions by HGM */
\r
12468 /* unknown feature: complain and skip */
\r
12470 while (*q && *q != '=') q++;
\r
12471 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12472 SendToProgram(buf, cps);
\r
12476 if (*p == '\"') {
\r
12478 while (*p && *p != '\"') p++;
\r
12479 if (*p == '\"') p++;
\r
12481 while (*p && *p != ' ') p++;
\r
12489 PeriodicUpdatesEvent(newState)
\r
12492 if (newState == appData.periodicUpdates)
\r
12495 appData.periodicUpdates=newState;
\r
12497 /* Display type changes, so update it now */
\r
12498 DisplayAnalysis();
\r
12500 /* Get the ball rolling again... */
\r
12502 AnalysisPeriodicEvent(1);
\r
12503 StartAnalysisClock();
\r
12508 PonderNextMoveEvent(newState)
\r
12511 if (newState == appData.ponderNextMove) return;
\r
12512 if (gameMode == EditPosition) EditPositionDone();
\r
12514 SendToProgram("hard\n", &first);
\r
12515 if (gameMode == TwoMachinesPlay) {
\r
12516 SendToProgram("hard\n", &second);
\r
12519 SendToProgram("easy\n", &first);
\r
12520 thinkOutput[0] = NULLCHAR;
\r
12521 if (gameMode == TwoMachinesPlay) {
\r
12522 SendToProgram("easy\n", &second);
\r
12525 appData.ponderNextMove = newState;
\r
12529 NewSettingEvent(option, command, value)
\r
12531 int option, value;
\r
12533 char buf[MSG_SIZ];
\r
12535 if (gameMode == EditPosition) EditPositionDone();
\r
12536 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12537 SendToProgram(buf, &first);
\r
12538 if (gameMode == TwoMachinesPlay) {
\r
12539 SendToProgram(buf, &second);
\r
12544 ShowThinkingEvent()
\r
12545 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12547 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12548 int newState = appData.showThinking
\r
12549 // [HGM] thinking: other features now need thinking output as well
\r
12550 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12552 if (oldState == newState) return;
\r
12553 oldState = newState;
\r
12554 if (gameMode == EditPosition) EditPositionDone();
\r
12556 SendToProgram("post\n", &first);
\r
12557 if (gameMode == TwoMachinesPlay) {
\r
12558 SendToProgram("post\n", &second);
\r
12561 SendToProgram("nopost\n", &first);
\r
12562 thinkOutput[0] = NULLCHAR;
\r
12563 if (gameMode == TwoMachinesPlay) {
\r
12564 SendToProgram("nopost\n", &second);
\r
12567 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12571 AskQuestionEvent(title, question, replyPrefix, which)
\r
12572 char *title; char *question; char *replyPrefix; char *which;
\r
12574 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12575 if (pr == NoProc) return;
\r
12576 AskQuestion(title, question, replyPrefix, pr);
\r
12580 DisplayMove(moveNumber)
\r
12583 char message[MSG_SIZ];
\r
12584 char res[MSG_SIZ];
\r
12585 char cpThinkOutput[MSG_SIZ];
\r
12587 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12589 if (moveNumber == forwardMostMove - 1 ||
\r
12590 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12592 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12594 if (strchr(cpThinkOutput, '\n')) {
\r
12595 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12598 *cpThinkOutput = NULLCHAR;
\r
12601 /* [AS] Hide thinking from human user */
\r
12602 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12603 *cpThinkOutput = NULLCHAR;
\r
12604 if( thinkOutput[0] != NULLCHAR ) {
\r
12607 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12608 cpThinkOutput[i] = '.';
\r
12610 cpThinkOutput[i] = NULLCHAR;
\r
12611 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12615 if (moveNumber == forwardMostMove - 1 &&
\r
12616 gameInfo.resultDetails != NULL) {
\r
12617 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12618 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12620 sprintf(res, " {%s} %s",
\r
12621 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12624 res[0] = NULLCHAR;
\r
12627 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12628 DisplayMessage(res, cpThinkOutput);
\r
12630 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12631 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12632 parseList[moveNumber], res);
\r
12633 DisplayMessage(message, cpThinkOutput);
\r
12638 DisplayAnalysisText(text)
\r
12641 char buf[MSG_SIZ];
\r
12643 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12644 || appData.icsEngineAnalyze) {
\r
12645 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12646 AnalysisPopUp(buf, text);
\r
12651 only_one_move(str)
\r
12654 while (*str && isspace(*str)) ++str;
\r
12655 while (*str && !isspace(*str)) ++str;
\r
12656 if (!*str) return 1;
\r
12657 while (*str && isspace(*str)) ++str;
\r
12658 if (!*str) return 1;
\r
12663 DisplayAnalysis()
\r
12665 char buf[MSG_SIZ];
\r
12666 char lst[MSG_SIZ / 2];
\r
12668 static char *xtra[] = { "", " (--)", " (++)" };
\r
12671 if (programStats.time == 0) {
\r
12672 programStats.time = 1;
\r
12675 if (programStats.got_only_move) {
\r
12676 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12678 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12680 nps = (u64ToDouble(programStats.nodes) /
\r
12681 ((double)programStats.time /100.0));
\r
12683 cs = programStats.time % 100;
\r
12684 s = programStats.time / 100;
\r
12685 h = (s / (60*60));
\r
12690 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12691 if (programStats.move_name[0] != NULLCHAR) {
\r
12692 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12693 programStats.depth,
\r
12694 programStats.nr_moves-programStats.moves_left,
\r
12695 programStats.nr_moves, programStats.move_name,
\r
12696 ((float)programStats.score)/100.0, lst,
\r
12697 only_one_move(lst)?
\r
12698 xtra[programStats.got_fail] : "",
\r
12699 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12701 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12702 programStats.depth,
\r
12703 programStats.nr_moves-programStats.moves_left,
\r
12704 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12706 only_one_move(lst)?
\r
12707 xtra[programStats.got_fail] : "",
\r
12708 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12711 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12712 programStats.depth,
\r
12713 ((float)programStats.score)/100.0,
\r
12715 only_one_move(lst)?
\r
12716 xtra[programStats.got_fail] : "",
\r
12717 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12720 DisplayAnalysisText(buf);
\r
12724 DisplayComment(moveNumber, text)
\r
12728 char title[MSG_SIZ];
\r
12729 char buf[8000]; // comment can be long!
\r
12730 int score, depth;
\r
12732 if( appData.autoDisplayComment ) {
\r
12733 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12734 strcpy(title, "Comment");
\r
12736 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12737 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12738 parseList[moveNumber]);
\r
12740 } else title[0] = 0;
\r
12742 // [HGM] PV info: display PV info together with (or as) comment
\r
12743 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12744 if(text == NULL) text = "";
\r
12745 score = pvInfoList[moveNumber].score;
\r
12746 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12747 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12748 CommentPopUp(title, buf);
\r
12750 if (text != NULL)
\r
12751 CommentPopUp(title, text);
\r
12754 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12755 * might be busy thinking or pondering. It can be omitted if your
\r
12756 * gnuchess is configured to stop thinking immediately on any user
\r
12757 * input. However, that gnuchess feature depends on the FIONREAD
\r
12758 * ioctl, which does not work properly on some flavors of Unix.
\r
12762 ChessProgramState *cps;
\r
12765 if (!cps->useSigint) return;
\r
12766 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12767 switch (gameMode) {
\r
12768 case MachinePlaysWhite:
\r
12769 case MachinePlaysBlack:
\r
12770 case TwoMachinesPlay:
\r
12771 case IcsPlayingWhite:
\r
12772 case IcsPlayingBlack:
\r
12773 case AnalyzeMode:
\r
12774 case AnalyzeFile:
\r
12775 /* Skip if we know it isn't thinking */
\r
12776 if (!cps->maybeThinking) return;
\r
12777 if (appData.debugMode)
\r
12778 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12779 InterruptChildProcess(cps->pr);
\r
12780 cps->maybeThinking = FALSE;
\r
12785 #endif /*ATTENTION*/
\r
12791 if (whiteTimeRemaining <= 0) {
\r
12792 if (!whiteFlag) {
\r
12793 whiteFlag = TRUE;
\r
12794 if (appData.icsActive) {
\r
12795 if (appData.autoCallFlag &&
\r
12796 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12797 SendToICS(ics_prefix);
\r
12798 SendToICS("flag\n");
\r
12802 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12804 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12805 if (appData.autoCallFlag) {
\r
12806 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12813 if (blackTimeRemaining <= 0) {
\r
12814 if (!blackFlag) {
\r
12815 blackFlag = TRUE;
\r
12816 if (appData.icsActive) {
\r
12817 if (appData.autoCallFlag &&
\r
12818 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12819 SendToICS(ics_prefix);
\r
12820 SendToICS("flag\n");
\r
12824 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12826 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12827 if (appData.autoCallFlag) {
\r
12828 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12839 CheckTimeControl()
\r
12841 if (!appData.clockMode || appData.icsActive ||
\r
12842 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12845 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12847 if ( !WhiteOnMove(forwardMostMove) )
\r
12848 /* White made time control */
\r
12849 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12850 /* [HGM] time odds: correct new time quota for time odds! */
\r
12851 / WhitePlayer()->timeOdds;
\r
12853 /* Black made time control */
\r
12854 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12855 / WhitePlayer()->other->timeOdds;
\r
12859 DisplayBothClocks()
\r
12861 int wom = gameMode == EditPosition ?
\r
12862 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12863 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12864 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12868 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12869 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12870 to use other calls if you don't. Clocks will be less accurate if
\r
12871 you have neither ftime nor gettimeofday.
\r
12874 /* VS 2008 requires the #include outside of the function */
\r
12875 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12876 #include <sys/timeb.h>
\r
12879 /* Get the current time as a TimeMark */
\r
12884 #if HAVE_GETTIMEOFDAY
\r
12886 struct timeval timeVal;
\r
12887 struct timezone timeZone;
\r
12889 gettimeofday(&timeVal, &timeZone);
\r
12890 tm->sec = (long) timeVal.tv_sec;
\r
12891 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12893 #else /*!HAVE_GETTIMEOFDAY*/
\r
12896 // include <sys/timeb.h> / moved to just above start of function
\r
12897 struct timeb timeB;
\r
12900 tm->sec = (long) timeB.time;
\r
12901 tm->ms = (int) timeB.millitm;
\r
12903 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12904 tm->sec = (long) time(NULL);
\r
12910 /* Return the difference in milliseconds between two
\r
12911 time marks. We assume the difference will fit in a long!
\r
12914 SubtractTimeMarks(tm2, tm1)
\r
12915 TimeMark *tm2, *tm1;
\r
12917 return 1000L*(tm2->sec - tm1->sec) +
\r
12918 (long) (tm2->ms - tm1->ms);
\r
12923 * Code to manage the game clocks.
\r
12925 * In tournament play, black starts the clock and then white makes a move.
\r
12926 * We give the human user a slight advantage if he is playing white---the
\r
12927 * clocks don't run until he makes his first move, so it takes zero time.
\r
12928 * Also, we don't account for network lag, so we could get out of sync
\r
12929 * with GNU Chess's clock -- but then, referees are always right.
\r
12932 static TimeMark tickStartTM;
\r
12933 static long intendedTickLength;
\r
12936 NextTickLength(timeRemaining)
\r
12937 long timeRemaining;
\r
12939 long nominalTickLength, nextTickLength;
\r
12941 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12942 nominalTickLength = 100L;
\r
12944 nominalTickLength = 1000L;
\r
12945 nextTickLength = timeRemaining % nominalTickLength;
\r
12946 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12948 return nextTickLength;
\r
12951 /* Adjust clock one minute up or down */
\r
12953 AdjustClock(Boolean which, int dir)
\r
12955 if(which) blackTimeRemaining += 60000*dir;
\r
12956 else whiteTimeRemaining += 60000*dir;
\r
12957 DisplayBothClocks();
\r
12960 /* Stop clocks and reset to a fresh time control */
\r
12964 (void) StopClockTimer();
\r
12965 if (appData.icsActive) {
\r
12966 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12967 } else { /* [HGM] correct new time quote for time odds */
\r
12968 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12969 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12971 if (whiteFlag || blackFlag) {
\r
12972 DisplayTitle("");
\r
12973 whiteFlag = blackFlag = FALSE;
\r
12975 DisplayBothClocks();
\r
12978 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12980 /* Decrement running clock by amount of time that has passed */
\r
12982 DecrementClocks()
\r
12984 long timeRemaining;
\r
12985 long lastTickLength, fudge;
\r
12988 if (!appData.clockMode) return;
\r
12989 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12991 GetTimeMark(&now);
\r
12993 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12995 /* Fudge if we woke up a little too soon */
\r
12996 fudge = intendedTickLength - lastTickLength;
\r
12997 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
12999 if (WhiteOnMove(forwardMostMove)) {
\r
13000 if(whiteNPS >= 0) lastTickLength = 0;
\r
13001 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13002 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13003 WhiteOnMove(currentMove));
\r
13005 if(blackNPS >= 0) lastTickLength = 0;
\r
13006 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13007 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13008 !WhiteOnMove(currentMove));
\r
13011 if (CheckFlags()) return;
\r
13013 tickStartTM = now;
\r
13014 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13015 StartClockTimer(intendedTickLength);
\r
13017 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13018 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13019 * with increment) the time remaining has increased to a level above the
\r
13020 * threshold, reset the alarm so it can sound again.
\r
13023 if (appData.icsActive && appData.icsAlarm) {
\r
13025 /* make sure we are dealing with the user's clock */
\r
13026 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13027 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13030 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13031 alarmSounded = FALSE;
\r
13032 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13033 PlayAlarmSound();
\r
13034 alarmSounded = TRUE;
\r
13040 /* A player has just moved, so stop the previously running
\r
13041 clock and (if in clock mode) start the other one.
\r
13042 We redisplay both clocks in case we're in ICS mode, because
\r
13043 ICS gives us an update to both clocks after every move.
\r
13044 Note that this routine is called *after* forwardMostMove
\r
13045 is updated, so the last fractional tick must be subtracted
\r
13046 from the color that is *not* on move now.
\r
13051 long lastTickLength;
\r
13053 int flagged = FALSE;
\r
13055 GetTimeMark(&now);
\r
13057 if (StopClockTimer() && appData.clockMode) {
\r
13058 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13059 if (WhiteOnMove(forwardMostMove)) {
\r
13060 if(blackNPS >= 0) lastTickLength = 0;
\r
13061 blackTimeRemaining -= lastTickLength;
\r
13062 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13063 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13064 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13065 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13067 if(whiteNPS >= 0) lastTickLength = 0;
\r
13068 whiteTimeRemaining -= lastTickLength;
\r
13069 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13070 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13071 pvInfoList[forwardMostMove-1].time =
\r
13072 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13074 flagged = CheckFlags();
\r
13076 CheckTimeControl();
\r
13078 if (flagged || !appData.clockMode) return;
\r
13080 switch (gameMode) {
\r
13081 case MachinePlaysBlack:
\r
13082 case MachinePlaysWhite:
\r
13083 case BeginningOfGame:
\r
13084 if (pausing) return;
\r
13088 case PlayFromGameFile:
\r
13089 case IcsExamining:
\r
13096 tickStartTM = now;
\r
13097 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13098 whiteTimeRemaining : blackTimeRemaining);
\r
13099 StartClockTimer(intendedTickLength);
\r
13103 /* Stop both clocks */
\r
13107 long lastTickLength;
\r
13110 if (!StopClockTimer()) return;
\r
13111 if (!appData.clockMode) return;
\r
13113 GetTimeMark(&now);
\r
13115 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13116 if (WhiteOnMove(forwardMostMove)) {
\r
13117 if(whiteNPS >= 0) lastTickLength = 0;
\r
13118 whiteTimeRemaining -= lastTickLength;
\r
13119 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13121 if(blackNPS >= 0) lastTickLength = 0;
\r
13122 blackTimeRemaining -= lastTickLength;
\r
13123 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13128 /* Start clock of player on move. Time may have been reset, so
\r
13129 if clock is already running, stop and restart it. */
\r
13133 (void) StopClockTimer(); /* in case it was running already */
\r
13134 DisplayBothClocks();
\r
13135 if (CheckFlags()) return;
\r
13137 if (!appData.clockMode) return;
\r
13138 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13140 GetTimeMark(&tickStartTM);
\r
13141 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13142 whiteTimeRemaining : blackTimeRemaining);
\r
13144 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13145 whiteNPS = blackNPS = -1;
\r
13146 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13147 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13148 whiteNPS = first.nps;
\r
13149 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13150 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13151 blackNPS = first.nps;
\r
13152 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13153 whiteNPS = second.nps;
\r
13154 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13155 blackNPS = second.nps;
\r
13156 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13158 StartClockTimer(intendedTickLength);
\r
13165 long second, minute, hour, day;
\r
13167 static char buf[32];
\r
13169 if (ms > 0 && ms <= 9900) {
\r
13170 /* convert milliseconds to tenths, rounding up */
\r
13171 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13173 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13177 /* convert milliseconds to seconds, rounding up */
\r
13178 /* use floating point to avoid strangeness of integer division
\r
13179 with negative dividends on many machines */
\r
13180 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13182 if (second < 0) {
\r
13184 second = -second;
\r
13187 day = second / (60 * 60 * 24);
\r
13188 second = second % (60 * 60 * 24);
\r
13189 hour = second / (60 * 60);
\r
13190 second = second % (60 * 60);
\r
13191 minute = second / 60;
\r
13192 second = second % 60;
\r
13195 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13196 sign, day, hour, minute, second);
\r
13197 else if (hour > 0)
\r
13198 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13200 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13207 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13210 StrStr(string, match)
\r
13211 char *string, *match;
\r
13215 length = strlen(match);
\r
13217 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13218 if (!strncmp(match, string, length))
\r
13225 StrCaseStr(string, match)
\r
13226 char *string, *match;
\r
13228 int i, j, length;
\r
13230 length = strlen(match);
\r
13232 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13233 for (j = 0; j < length; j++) {
\r
13234 if (ToLower(match[j]) != ToLower(string[j]))
\r
13237 if (j == length) return string;
\r
13243 #ifndef _amigados
\r
13245 StrCaseCmp(s1, s2)
\r
13251 c1 = ToLower(*s1++);
\r
13252 c2 = ToLower(*s2++);
\r
13253 if (c1 > c2) return 1;
\r
13254 if (c1 < c2) return -1;
\r
13255 if (c1 == NULLCHAR) return 0;
\r
13264 return isupper(c) ? tolower(c) : c;
\r
13272 return islower(c) ? toupper(c) : c;
\r
13274 #endif /* !_amigados */
\r
13282 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13289 StrSavePtr(s, savePtr)
\r
13290 char *s, **savePtr;
\r
13295 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13296 strcpy(*savePtr, s);
\r
13298 return(*savePtr);
\r
13306 char buf[MSG_SIZ];
\r
13308 clock = time((time_t *)NULL);
\r
13309 tm = localtime(&clock);
\r
13310 sprintf(buf, "%04d.%02d.%02d",
\r
13311 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13312 return StrSave(buf);
\r
13317 PositionToFEN(move, overrideCastling)
\r
13319 char *overrideCastling;
\r
13321 int i, j, fromX, fromY, toX, toY;
\r
13326 ChessSquare piece;
\r
13328 whiteToPlay = (gameMode == EditPosition) ?
\r
13329 !blackPlaysFirst : (move % 2 == 0);
\r
13332 /* Piece placement data */
\r
13333 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13335 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13336 if (boards[move][i][j] == EmptySquare) {
\r
13338 } else { ChessSquare piece = boards[move][i][j];
\r
13339 if (emptycount > 0) {
\r
13340 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13341 *p++ = '0' + emptycount;
\r
13342 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13345 if(PieceToChar(piece) == '+') {
\r
13346 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13348 piece = (ChessSquare)(DEMOTED piece);
\r
13350 *p++ = PieceToChar(piece);
\r
13351 if(p[-1] == '~') {
\r
13352 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13353 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13358 if (emptycount > 0) {
\r
13359 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13360 *p++ = '0' + emptycount;
\r
13361 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13368 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13369 if( gameInfo.holdingsWidth ) {
\r
13370 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13372 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13373 piece = boards[move][i][BOARD_WIDTH-1];
\r
13374 if( piece != EmptySquare )
\r
13375 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13376 *p++ = PieceToChar(piece);
\r
13378 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13379 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13380 if( piece != EmptySquare )
\r
13381 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13382 *p++ = PieceToChar(piece);
\r
13385 if( q == p ) *p++ = '-';
\r
13390 /* Active color */
\r
13391 *p++ = whiteToPlay ? 'w' : 'b';
\r
13394 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
\r
13395 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
\r
13397 if(nrCastlingRights) {
\r
13399 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13400 /* [HGM] write directly from rights */
\r
13401 if(castlingRights[move][2] >= 0 &&
\r
13402 castlingRights[move][0] >= 0 )
\r
13403 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13404 if(castlingRights[move][2] >= 0 &&
\r
13405 castlingRights[move][1] >= 0 )
\r
13406 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13407 if(castlingRights[move][5] >= 0 &&
\r
13408 castlingRights[move][3] >= 0 )
\r
13409 *p++ = castlingRights[move][3] + AAA;
\r
13410 if(castlingRights[move][5] >= 0 &&
\r
13411 castlingRights[move][4] >= 0 )
\r
13412 *p++ = castlingRights[move][4] + AAA;
\r
13415 /* [HGM] write true castling rights */
\r
13416 if( nrCastlingRights == 6 ) {
\r
13417 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13418 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13419 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13420 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13421 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13422 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13423 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13424 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13427 if (q == p) *p++ = '-'; /* No castling rights */
\r
13431 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13432 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13433 /* En passant target square */
\r
13434 if (move > backwardMostMove) {
\r
13435 fromX = moveList[move - 1][0] - AAA;
\r
13436 fromY = moveList[move - 1][1] - ONE;
\r
13437 toX = moveList[move - 1][2] - AAA;
\r
13438 toY = moveList[move - 1][3] - ONE;
\r
13439 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13440 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13441 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13443 /* 2-square pawn move just happened */
\r
13444 *p++ = toX + AAA;
\r
13445 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13456 /* [HGM] find reversible plies */
\r
13457 { int i = 0, j=move;
\r
13459 if (appData.debugMode) { int k;
\r
13460 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13461 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13462 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13466 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13467 if( j == backwardMostMove ) i += initialRulePlies;
\r
13468 sprintf(p, "%d ", i);
\r
13469 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13471 /* Fullmove number */
\r
13472 sprintf(p, "%d", (move / 2) + 1);
\r
13474 return StrSave(buf);
\r
13478 ParseFEN(board, blackPlaysFirst, fen)
\r
13480 int *blackPlaysFirst;
\r
13486 ChessSquare piece;
\r
13490 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13491 if(gameInfo.holdingsWidth) {
\r
13492 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13493 board[i][0] = EmptySquare; /* black holdings */
\r
13494 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13495 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13496 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13500 /* Piece placement data */
\r
13501 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13504 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13505 if (*p == '/') p++;
\r
13506 emptycount = gameInfo.boardWidth - j;
\r
13507 while (emptycount--)
\r
13508 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13510 #if(BOARD_SIZE >= 10)
\r
13511 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13512 p++; emptycount=10;
\r
13513 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13514 while (emptycount--)
\r
13515 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13517 } else if (isdigit(*p)) {
\r
13518 emptycount = *p++ - '0';
\r
13519 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13520 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13521 while (emptycount--)
\r
13522 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13523 } else if (*p == '+' || isalpha(*p)) {
\r
13524 if (j >= gameInfo.boardWidth) return FALSE;
\r
13526 piece = CharToPiece(*++p);
\r
13527 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13528 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13529 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13530 } else piece = CharToPiece(*p++);
\r
13532 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13533 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13534 piece = (ChessSquare) (PROMOTED piece);
\r
13535 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13538 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13544 while (*p == '/' || *p == ' ') p++;
\r
13546 /* [HGM] look for Crazyhouse holdings here */
\r
13547 while(*p==' ') p++;
\r
13548 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13549 if(*p == '[') p++;
\r
13550 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13551 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13552 /* if we would allow FEN reading to set board size, we would */
\r
13553 /* have to add holdings and shift the board read so far here */
\r
13554 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13556 if((int) piece >= (int) BlackPawn ) {
\r
13557 i = (int)piece - (int)BlackPawn;
\r
13558 i = PieceToNumber((ChessSquare)i);
\r
13559 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13560 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13561 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13563 i = (int)piece - (int)WhitePawn;
\r
13564 i = PieceToNumber((ChessSquare)i);
\r
13565 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13566 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13567 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13571 if(*p == ']') *p++;
\r
13574 while(*p == ' ') p++;
\r
13576 /* Active color */
\r
13579 *blackPlaysFirst = FALSE;
\r
13582 *blackPlaysFirst = TRUE;
\r
13588 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13589 /* return the extra info in global variiables */
\r
13591 /* set defaults in case FEN is incomplete */
\r
13592 FENepStatus = EP_UNKNOWN;
\r
13593 for(i=0; i<nrCastlingRights; i++ ) {
\r
13594 FENcastlingRights[i] =
\r
13595 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13596 } /* assume possible unless obviously impossible */
\r
13597 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13598 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13599 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13600 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13601 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13602 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13603 FENrulePlies = 0;
\r
13605 while(*p==' ') p++;
\r
13606 if(nrCastlingRights) {
\r
13607 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13608 /* castling indicator present, so default becomes no castlings */
\r
13609 for(i=0; i<nrCastlingRights; i++ ) {
\r
13610 FENcastlingRights[i] = -1;
\r
13613 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13614 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13615 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13616 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13617 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13619 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13620 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13621 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13625 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13626 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13627 FENcastlingRights[2] = whiteKingFile;
\r
13630 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13631 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13632 FENcastlingRights[2] = whiteKingFile;
\r
13635 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13636 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13637 FENcastlingRights[5] = blackKingFile;
\r
13640 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13641 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13642 FENcastlingRights[5] = blackKingFile;
\r
13645 default: /* FRC castlings */
\r
13646 if(c >= 'a') { /* black rights */
\r
13647 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13648 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13649 if(i == BOARD_RGHT) break;
\r
13650 FENcastlingRights[5] = i;
\r
13652 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13653 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13655 FENcastlingRights[3] = c;
\r
13657 FENcastlingRights[4] = c;
\r
13658 } else { /* white rights */
\r
13659 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13660 if(board[0][i] == WhiteKing) break;
\r
13661 if(i == BOARD_RGHT) break;
\r
13662 FENcastlingRights[2] = i;
\r
13663 c -= AAA - 'a' + 'A';
\r
13664 if(board[0][c] >= WhiteKing) break;
\r
13666 FENcastlingRights[0] = c;
\r
13668 FENcastlingRights[1] = c;
\r
13672 if (appData.debugMode) {
\r
13673 fprintf(debugFP, "FEN castling rights:");
\r
13674 for(i=0; i<nrCastlingRights; i++)
\r
13675 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13676 fprintf(debugFP, "\n");
\r
13679 while(*p==' ') p++;
\r
13682 /* read e.p. field in games that know e.p. capture */
\r
13683 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13684 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13686 p++; FENepStatus = EP_NONE;
\r
13688 char c = *p++ - AAA;
\r
13690 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13691 if(*p >= '0' && *p <='9') *p++;
\r
13697 if(sscanf(p, "%d", &i) == 1) {
\r
13698 FENrulePlies = i; /* 50-move ply counter */
\r
13699 /* (The move number is still ignored) */
\r
13706 EditPositionPasteFEN(char *fen)
\r
13708 if (fen != NULL) {
\r
13709 Board initial_position;
\r
13711 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13712 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13715 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13716 EditPositionEvent();
\r
13717 blackPlaysFirst = savedBlackPlaysFirst;
\r
13718 CopyBoard(boards[0], initial_position);
\r
13719 /* [HGM] copy FEN attributes as well */
\r
13721 initialRulePlies = FENrulePlies;
\r
13722 epStatus[0] = FENepStatus;
\r
13723 for( i=0; i<nrCastlingRights; i++ )
\r
13724 castlingRights[0][i] = FENcastlingRights[i];
\r
13726 EditPositionDone();
\r
13727 DisplayBothClocks();
\r
13728 DrawPosition(FALSE, boards[currentMove]);
\r